C语言函数调用参数传递栈详解
- 格式:docx
- 大小:31.15 KB
- 文档页数:11
C语⾔中函数参数传递C语⾔中函数参数传递的三种⽅式(1)值传递,就是把你的变量的值传递给函数的形式参数,实际就是⽤变量的值来新⽣成⼀个形式参数,因⽽在函数⾥对形参的改变不会影响到函数外的变量的值。
(2)地址传递,就是把变量的地址赋给函数⾥形式参数的指针,使指针指向真实的变量的地址,因为对指针所指地址的内容的改变能反映到函数外,能改变函数外的变量的值。
(3)引⽤传递,实际是通过指针来实现的,能达到使⽤的效果如传址,可是使⽤⽅式如传值。
说⼏点建议:如果传值的话,会⽣成新的对象,花费时间和空间,⽽在退出函数的时候,⼜会销毁该对象,花费时间和空间。
因⽽如果int,char等固有类型,⽽是你⾃⼰定义的类或结构等,都建议传指针或引⽤,因为他们不会创建新的对象。
例1:下⾯这段代码的输出结果为:#include<stdio.h>void change(int*a, int&b, int c){c=*a;b=30;*a=20;}int main ( ){int a=10, b=20, c=30;change(&a,b,c);printf(“%d,%d,%d,”,a,b,c);return 0;}结果:20 30 30解析:1,指针传参 -> 将变量的地址直接传⼊函数,函数中可以对其值进⾏修改。
2,引⽤传参 -> 将变量的引⽤传⼊函数,效果和指针相同,同样函数中可以对其值进⾏修改。
3,值传参 -> 在传参过程中,⾸先将c的值复制给函数c变量,然后在函数中修改的即是函数的c变量,然后函数返回时,系统⾃动释放变量c。
⽽对main函数的c没有影响。
例2:#include<stdio.h>void myswap(int x, int y){int t;t=x;x=y;y=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(a,b); //作为对⽐,直接交换两个整数,显然不⾏printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}#include<stdio.h>void myswap(int *p1, int *p2){int t;t=*p1;*p1=*p2;*p2=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(&a,&b); //交换两个整数的地址printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}#include<stdio.h>void myswap(int &x, int &y){int t;t=x;x=y;y=t;}int main(){int a, b;printf("请输⼊待交换的两个整数:");scanf("%d %d", &a, &b);myswap(a,b); //直接以变量a和b作为实参交换printf("调⽤交换函数后的结果是:%d 和 %d\n", a, b);return 0;}第⼀个的运⾏结果:输⼊2 3,输出2 3第⼆个的运⾏结果:输⼊2 3,输出3 2第三个的运⾏结果:输⼊2 3,输出3 2解析:在第⼀个程序中,传值不成功的原因是指在形参上改变了数值,没有在实参上改变数值。
stdcall调用约定:stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。
在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebpret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。
函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。
C语言函数调用的底层机制1.函数栈帧的创建和销毁:函数栈帧是在函数被调用时在内存中创建的,用于存储函数的局部变量、参数以及返回地址等信息。
栈帧的创建一般包括以下步骤:(1)将函数的返回地址、参数、局部变量的值等数据压栈。
(2)分配存储局部变量的空间。
(3)保存当前栈指针,并将栈指针指向新分配的栈帧。
函数执行完毕后,会执行相应的销毁操作,包括恢复堆栈指针、释放局部变量占用的内存等。
2.参数传递:(1)按值传递:将实参的值拷贝到栈帧中的参数位置,函数对参数的修改不会影响到实参。
(2)按地址传递:将实参的地址传递给函数,函数可以通过地址访问实参,其修改会影响到实参。
3.返回值的处理:C语言函数的返回值一般通过寄存器来处理。
在函数调用前,调用者会保存返回地址和一些寄存器的值等信息,然后调用函数。
函数执行完成后,会将返回值存储在指定的寄存器中,并将栈帧中保存的返回地址恢复到程序计数器中,从而实现函数调用后的返回操作。
4.函数的跳转和返回:函数的跳转和返回一般由CALL和RET指令来实现。
在函数调用时,通过CALL指令将函数的地址压栈并跳转到指定的函数入口地址。
函数执行完成后,通过RET指令将栈顶的返回地址弹出到程序计数器中,从而返回到函数调用的地方。
5.函数调用的栈帧布局:函数调用时的栈帧布局一般包括局部变量、参数、返回地址和上一级栈帧指针等信息。
栈帧的布局在编译器中是预先定义好的,根据平台的不同会有所不同。
总结起来,C语言函数调用的底层机制主要涉及函数栈帧的创建和销毁、参数传递、返回值的处理以及函数的跳转和返回等。
这些机制通过栈的操作实现函数的调用和返回,保证了函数的正确执行和数据的传递。
了解这些底层机制对于理解函数调用过程以及编写高效的函数调用代码非常重要。
C语言教程十一、函数参数的传递和值返回前面我们说的都是无参数无返回值的函数,实际程序中,我们经常使用到带参数有返回值的函数。
一、函数参数传递1.形式参数和实际参数函数的调用值把一些表达式作为参数传递给函数。
函数定义中的参数是形式参数,函数的调用者提供给函数的参数叫实际参数。
在函数调用之前,实际参数的值将被拷贝到这些形式参数中。
2.参数传递先看一个例子:void a(int); /*注意函数声明的形式*/main(){int num;scanf(%d,&num);a(num); /*注意调用形式*/}void a(int num_back) /*注意定义形式*/{printf(%d\n,num_back);}在主函数中,先定义一个变量,然后输入一个值,在a()这个函数中输出。
当程序运行a(num);这一步时,把num的值赋值给num_back,在运行程序过程中,把实际参数的值传给形式参数,这就是函数参数的传递。
形参和实参可能不只一个,如果多于一个时,函数声明、调用、定义的形式都要一一对应,不仅个数要对应,参数的数据类型也要对应。
void a(int,float);main(){int num1;float num2;scanf(%d,&num1);scanf(%f,&num2);a(num1,num2);}void a(int num1_back,float num2_back){printf(%d,%f\n,num1_back,num2_back);}上面的例子中,函数有两个参数,一个是整型,一个是浮点型,那么在声明、调用、定义的时候,不仅个数要一样,类型也要对应。
如果不对应,有可能使的编译错误,即使没错误,也有可能让数据传递过程中出现错误。
再看一个例子:void a(int);main(){int num;scanf(%d,&num);a(num);}void a(int num){printf(%d\n,num);}看上面的例子,形式参数和实际参数的标识符都是num,程序把实际参数num 的值传递给形式参数num。
c语言函数多个参数传递摘要:1.引言2.C 语言函数参数传递的方式3.多个参数的传递4.传递参数的注意事项5.结论正文:【引言】C 语言是一种广泛使用的编程语言,它具有简洁、高效的特点。
在C 语言程序设计中,函数的使用是必不可少的。
函数可以实现代码的模块化,使程序更加清晰易懂。
在函数调用时,参数的传递是一个重要的环节。
本篇文章主要介绍C 语言函数多个参数的传递方法及其注意事项。
【C 语言函数参数传递的方式】C 语言函数参数传递方式主要有两种:值传递和指针传递。
1.值传递:函数在调用时,会将实参的值复制到形参中。
这意味着形参和实参是两个独立的变量,它们之间互不影响。
值传递适用于基本数据类型,如int、float 等。
2.指针传递:函数在调用时,会将实参的地址传递给形参。
这意味着形参和实参共享同一内存空间,对形参的修改将影响实参。
指针传递适用于数组和结构体等复合数据类型。
【多个参数的传递】在实际编程中,函数可能需要接收多个参数。
C 语言中,多个参数的传递可以通过以下方式实现:1.按顺序传递:将多个参数按照声明的顺序依次传递给函数。
这种方式较为简单,但当参数较多时,容易出错。
2.使用数组:将多个参数封装在一个数组中,然后将数组作为参数传递给函数。
这种方式可以减少参数传递的错误,但需要注意数组的大小和类型。
3.使用结构体:将多个参数封装在一个结构体中,然后将结构体作为参数传递给函数。
这种方式可以方便地管理多个参数,同时具有较好的封装性。
【传递参数的注意事项】在函数参数传递过程中,需要注意以下几点:1.参数类型匹配:确保实参的类型与形参的类型匹配,否则会导致编译错误。
2.参数顺序正确:按照函数声明的顺序传递参数,否则会导致函数调用失败。
3.注意参数传递的方式:根据参数的类型选择合适的传递方式,避免因为传递方式不当导致的程序错误。
【结论】C 语言函数多个参数的传递是程序设计中常见的场景。
通过掌握不同的参数传递方式和注意事项,可以有效提高程序的编写效率和稳定性。
C语言中如何进行函数的调用和参数传递C语言是一种广泛应用于系统编程和嵌入式开发的高级编程语言。
在C语言中,函数的调用和参数传递是非常重要的概念。
本文将介绍C语言中如何进行函数的调用和参数传递的基本原理和方法。
在C语言中,函数是程序的基本组成单元之一。
通过函数的调用,可以将程序的执行流程切换到函数中,并执行函数中的代码。
函数的调用可以帮助我们实现代码的模块化和重用,提高程序的可读性和可维护性。
在C语言中,函数的调用需要遵循一定的规则。
首先,我们需要在函数调用前声明函数的原型或定义函数的实现。
函数的原型告诉编译器函数的名称、返回值类型和参数列表等信息,以便编译器能够正确地处理函数的调用和参数传递。
函数的调用可以使用函数名称后跟一对圆括号的方式进行。
在圆括号中,可以传递函数所需的参数。
参数可以是常量、变量或表达式等。
在函数调用时,传递的参数将被复制到函数的形参中,函数在执行时可以使用这些参数进行计算或处理。
在C语言中,参数的传递可以通过值传递或引用传递进行。
值传递是指将参数的值复制到函数的形参中,函数在执行时使用的是形参的副本,对形参的修改不会影响到实参。
而引用传递是指将参数的地址传递给函数,函数在执行时使用的是实参的地址,对形参的修改会影响到实参。
在C语言中,函数的参数传递是通过栈来实现的。
栈是一种后进先出的数据结构,用于存储函数的局部变量、参数和返回值等信息。
在函数调用时,参数被依次压入栈中,然后函数开始执行。
在函数执行完毕后,栈会弹出参数,将控制权返回给调用函数。
除了值传递和引用传递外,C语言还支持指针传递。
指针传递是指将参数的指针传递给函数,函数在执行时可以通过指针来访问和修改实参。
通过指针传递参数,可以避免复制大量的数据,提高程序的效率。
在C语言中,函数的调用可以有返回值和无返回值两种形式。
有返回值的函数可以通过return语句返回一个值给调用者。
无返回值的函数可以使用void关键字来声明,表示函数不返回任何值。
c语言函数参数传递方式C语言是一种广泛使用的编程语言,函数参数传递方式是C语言中非常重要的概念之一。
函数参数传递方式可以分为按值传递、按址传递和按引用传递三种方式。
本文将针对这三种方式进行详细讲解。
一、按值传递按值传递是指在函数调用时,将实际参数的值复制给形式参数,函数内部对形参的修改不会影响到实际参数的值。
这种方式适用于参数较少、参数值不需要在函数内部被修改的情况。
在按值传递的方式下,函数在栈内存中为形参分配空间,并将实参的值复制到形参中。
函数执行结束后,栈内存中的形参被销毁,不会影响到实参的值。
二、按址传递按址传递是指在函数调用时,将实际参数的地址传递给形式参数,函数内部通过指针对实参进行操作,可以修改实参的值。
这种方式适用于需要在函数内部修改实参值的情况。
在按址传递的方式下,函数在栈内存中为形参分配空间,并将实参的地址传递给形参。
函数内部通过指针对实参进行操作,修改实参的值。
由于传递的是地址,所以函数内部对形参的修改会影响到实参。
三、按引用传递按引用传递是C++中的特性,其本质是通过指针来实现的。
在C语言中,可以通过传递指针的方式来模拟按引用传递。
按引用传递的特点是可以修改实参的值,并且不需要像按址传递那样使用指针操作。
在按引用传递的方式下,函数在栈内存中为形参分配空间,并将实参的地址传递给形参。
函数内部通过引用的方式操作形参,可以直接修改实参的值。
由于传递的是地址,所以函数内部对形参的修改会影响到实参。
需要注意的是,按引用传递需要使用指针来实现。
在函数调用时,需要将实参的地址传递给形参,即传递一个指向实参的指针。
函数内部通过解引用指针来操作实参,可以达到修改实参的目的。
总结:C语言中的函数参数传递方式包括按值传递、按址传递和按引用传递三种方式。
按值传递适用于参数较少、参数值不需要在函数内部被修改的情况;按址传递适用于需要在函数内部修改实参值的情况;按引用传递需要使用指针来实现,通过传递实参的地址来实现对实参的修改。
c语言函数调用时参数传递方式的有哪几种,分别简述他们的传
递方式
C语言函数调用时参数的传递方式主要有以下几种:
1. 值传递:函数调用时,将实际参数的值复制给形式参数,函数内部对形式参数进行修改不会影响实际参数的值。
这是最常见的参数传递方式。
2. 引用传递:通过传递变量的指针作为参数,函数内部可以直接通过指针访问和修改实际参数的值。
这种方式可以实现在函数内部改变实参的值。
3. 地址传递:传递变量的地址作为参数,在函数内部通过指针来访问和修改实际参数的值。
和引用传递类似,通过地址传递也可以改变实参的值。
4. 数组传递:将数组的首地址作为参数传递给函数,函数内部可以通过指针来访问和修改数组的元素。
5. 结构体传递:将整个结构体作为参数传递给函数,在函数内部可以直接访问和修改结构体中的成员。
需要注意的是,C语言中的参数传递都是按值传递的,包括引
用传递和地址传递。
所谓按值传递,是指在函数调用时将实参的值复制给形参,函数内部对形参的操作不会影响到实参的值。
但是通过引用传递和地址传递,可以通过指针来访问和修改实参的值,使得函数可以改变实参的值。
过程调用详解过程调用是计算机中常见的一种操作,它通常用于调用函数或子程序。
在程序执行过程中,当需要调用某个函数或子程序时,程序会通过过程调用的方式将控制权转移到被调用的函数或子程序中,等待其执行完毕后再返回结果。
过程调用的实现涉及到许多细节,包括参数传递、栈操作、返回值获取等,本文将对这些细节进行详解。
一、参数传递在过程调用中,参数传递是必不可少的一环。
按照不同的编程语言和编译器,参数传递的方式也有所不同。
常见的有以下几种:1. 值传递:将实参的值复制到形参中,函数内部对形参的操作不会影响实参。
2. 引用传递:传递的是实参的地址,函数内部对形参的操作会影响实参。
3. 指针传递:传递的是指向实参内存地址的指针,函数内部通过指针访问实参的值。
4. 寄存器传递:将实参的值存放在寄存器中,通过寄存器传递参数。
这种方式通常用于参数数量较少的情况下。
二、栈操作在过程调用中,栈是一个重要的数据结构。
在调用过程中,栈用于存储函数的返回地址、参数和局部变量等信息。
常见的栈操作包括以下几种:1. 压栈:将数据压入栈中,通常用于保存函数的返回地址、参数和局部变量等信息。
2. 弹栈:将栈顶的数据弹出栈,通常用于恢复函数的返回地址、参数和局部变量等信息。
3. 栈平衡:在函数调用结束后,需要将函数调用过程中压入栈中的数据全部弹出,以保证栈的平衡。
三、返回值获取在函数执行完毕后,需要将返回值传回到调用者处。
常见的返回值获取方式包括以下几种:1. 寄存器返回:将返回值存放在寄存器中,调用者通过寄存器获取返回值。
2. 栈返回:将返回值压入栈中,调用者通过弹栈获取返回值。
3. 寄存器+栈返回:将返回值同时存放在寄存器和栈中,调用者可以通过寄存器或弹栈获取返回值。
总之,过程调用涉及到众多细节,包括参数传递、栈操作、返回值获取等,只有了解这些细节,才能编写出高效、稳定的代码。
对_stdcall 的理解在C语言中,假设我们有这样的一个函数:int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数。
但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。
也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。
为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。
栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。
用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。
用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。
函数计算结束以后,或者调用者、或者函数本身修改栈,使堆栈恢复原装。
在参数传递中,有两个很重要的问题必须得到明确说明:当参数个数多于一个时,按照什么顺序把参数压入堆栈函数调用后,由谁来把堆栈恢复原装。
在高级语言中,通过函数调用约定来说明这两个问题。
常见的调用约定有:stdcall,cdecl,fastcall,thiscall,naked callstdcall调用约定:stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。
在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
在C语言中,假设我们有这样的一个函数:int function(int a,int b)调用时只要用result = function(1,2)这样的方式就可以使用这个函数。
但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。
也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。
为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。
栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。
用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。
用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。
函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
在参数传递中,有两个很重要的问题必须得到明确说明:当参数个数多于一个时,按照什么顺序把参数压入堆栈函数调用后,由谁来把堆栈恢复原装在高级语言中,通过函数调用约定来说明这两个问题。
常见的调用约定有:stdcallcdeclfastcallthiscallnaked callstdcall调用约定stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK.stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebp ret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
c语⾔stack(栈)和heap(堆)的使⽤详解⼀、预备知识—程序的内存分配⼀个由C/C++编译的程序占⽤的内存分为以下⼏个部分1、栈区(stack)—由编译器⾃动分配释放,存放函数的参数值,局部变量的值等。
其操作⽅式类似于数据结构中的栈。
2、堆区(heap)—⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配⽅式倒是类似于链表。
3、全局区(静态区)(static)—全局变量和静态变量的存储是放在⼀块的,初始化的全局变量和静态变量在⼀块区域,未初始化的全局变量和未初始化的静态变量在相邻的另⼀块区域。
程序结束后由系统释放。
4、⽂字常量区—常量字符串就是放在这⾥的。
程序结束后由系统释放。
5、程序代码区—存放函数体的⼆进制代码。
⼆、例⼦程序复制代码代码如下://main.cppint a=0; //全局初始化区char *p1; //全局未初始化区main(){intb;栈char s[]="abc"; //栈char *p2; //栈char *p3="123456"; //123456\0在常量区,p3在栈上。
static int c=0; //全局(静态)初始化区p1 = (char*)malloc(10);p2 = (char*)malloc(20); //分配得来得10和20字节的区域就在堆区。
strcpy(p1,"123456"); //123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成⼀个地⽅。
}三、堆和栈的理论知识2.1申请⽅式stack:由系统⾃动分配。
例如,声明在函数中⼀个局部变量int b;系统⾃动在栈中为b开辟空间heap:需要程序员⾃⼰申请,并指明⼤⼩,在c中⽤malloc函数如p1=(char*)malloc(10);在C++中⽤new运算符如p2=(char*)malloc(10);但是注意p1、p2本⾝是在栈中的。
c语言main函数的参数入栈顺序C语言是一种非常受欢迎的编程语言,它的特点是简单易懂,但功能强大。
而其中最为重要的函数是main函数,它是程序的入口函数。
在main函数中,我们可以进行各种操作,调用其他函数完成各种任务。
那么,在C语言中,主函数的参数是如何入栈的呢?下面我们来逐步分析。
首先,对于C语言的主函数,有以下两个常见的形式:1. int main()2. int main(int argc, char* argv[])第一种形式不带参数,而第二种形式带有两个参数:第一个参数是参数个数argc,第二个参数是指向参数的指针数组argv[]。
下面,我们来看看每一种形式的参数入栈顺序。
在第一种形式中,main函数没有任何参数,因此不需要进行参数入栈操作。
在函数调用时,只需要把返回地址入栈即可,返回地址是指在函数调用结束后程序需要继续执行的代码的地址。
在第二种形式中,需要将两个参数入栈,即参数个数argc和指向参数的指针数组argv[]。
这里的指针数组是一个数组,其中每个元素都是指向字符类型的指针,它们指向了传递给程序的命令行信息。
参数的入栈顺序是从右往左的。
也就是说,当调用main函数时,先将argv[]的地址入栈,然后是argc的值。
这是因为数组指针argv[]在参数入栈时会被放在高地址内存中,而argc在参数入栈时会被放在低地址内存中。
因此,为了避免地址溢出和段错误,需要先入栈argv[]的地址,再入栈argc的值。
总结来说,C语言的main函数的参数入栈顺序是从右往左的,先入栈指向参数的指针数组argv[]的地址,再入栈参数个数argc的值,而对于不带参数的形式,只需要将返回地址入栈即可。
在C语言的函数调用中,参数的入栈顺序是非常重要的,它决定了代码的正确性和效率。
因此,在进行函数调用时,需要注意参数的类型和入栈顺序,保证代码的正确性和可读性。
在实际的编程中,需要根据具体的场景和实际需求来确定参数的类型和入栈顺序,从而提高程序的效率和正确性。
C语言函数参数传递问题C语言函数参数传递问题导语:在一些C语言教材和参考资料中,常常把函数参数传递分为“数值传递”和“地址传递”两种方式,这种概念模糊的分类方法给初学者的理解造成一定程度的困难。
下面就由店铺为大家介绍一下C语言函数参数传递问题,欢迎大家阅读!1概述函数是C语言程序的基本构成模块,通过使用函数,不仅可以省去重复代码的编写,还可以使程序更加模块化,从而有利于程序的阅读、修改和完善。
对于C语言的初学者来说,准确理解函数参数的传递方式是难点之一。
一些C语言程序设计教材[1][2]中把函数间的参数传递分为“数值传递”和“地址传递”两种方式,这种分类很容易使初学者混淆概念,更不能抓住参数传递的本质。
2传递方式和数据类型“值传递”是函数参数的唯一传递方式。
函数的参数分为实际参数(简称实参)和形式参数(简称形参),在定义函数时使用的参数叫形参,在调用函数时使用的参数叫实参。
实参可以是常量、变量或表达式,但要求它们有确定的值。
实参与形参的结合只有一种方式,即“值传递”,把实参的值赋给形参变量。
“值传递”具有单向性,只能把实参传给形参,而不能由形参回传给实参。
不同数据类型的`函数参数对主调函数中变量的作用有所不同。
函数参数可取的数据类型为基本类型、构造类型、指针类型或空类型,如图所示。
空类型表示该函数为无参函数,在此不作介绍。
其余数据类型又可分为三类:普通类型,包括基本类型、结构体类型和共用体类型;指针类型;数组类型。
函数参数为普通类型时,对函数的调用不用影响到主调函数中变量的值;函数参数为指针类型或数组类型时,对函数的调用可以修改主调函数中有关变量的值。
3普通类型变量作为函数参数普通变量作为函数参数时,对形参的修改不会影响到实参。
如下例所示,通过“值传递”,形参x保存了实参a的值,形参y保存了实参b的值,因为形参和实参是相互独立的不同变量,所以在swap函数中对形参x和y进行的交换不会反映到实参a和b中。
C语言函数调用与参数传递函数调用和参数传递是C语言中非常重要的概念,它们是实现程序模块化和代码重用的关键。
本文将详细介绍C语言中的函数调用和参数传递的机制。
在C语言中,函数是一段独立的代码块,用于完成特定的任务。
函数可以被多次调用,从而实现代码的重用。
函数调用是通过函数名和一对圆括号来实现的,函数名后面跟着的圆括号中可以包含函数的参数。
C语言中的函数调用是一种栈式的调用方式,即先进后出。
当程序执行到一个函数调用语句时,会先将当前函数的返回地址和一些其他的信息压入栈中,然后跳转到被调用的函数中执行。
当被调用的函数执行完毕后,会从栈中弹出返回地址,并跳转回调用函数的位置继续执行。
在函数调用过程中,参数的传递是通过栈来完成的。
当一个函数被调用时,会将参数按照从右到左的顺序依次压入栈中。
被调用的函数可以通过栈来获取参数的值,并在执行过程中使用这些参数。
当函数执行完毕后,栈中的参数会被弹出。
C语言中的参数传递有两种方式:值传递和指针传递。
值传递是指将参数的值复制一份传递给被调用的函数,被调用的函数在执行过程中使用的是这个复制的值,而不是原始的值。
这样做的好处是被调用的函数可以修改参数的值,而不会影响到调用函数的参数。
指针传递是指将参数的地址传递给被调用的函数,被调用的函数可以通过指针来访问和修改参数的值,这样做可以节省内存的使用,但需要注意指针的使用是否正确。
在C语言中,函数的参数可以有默认值。
当函数被调用时,如果没有提供参数的值,则会使用默认值。
这样可以方便地定义一些通用的函数,同时又可以提供一些默认的参数值供用户选择。
在函数的声明和定义中可以使用关键字"default"来指定参数的默认值。
除了参数传递外,函数调用还可以返回一个值。
在C语言中,函数可以有返回值,返回值的类型可以是任意的。
当函数执行完毕后,可以使用关键字"return"将一个值返回给调用函数。
被调用的函数可以通过返回值来将计算结果传递给调用函数。
函数调⽤时参数的⼊栈和出栈顺序先看看递归的实现和栈的关系,这⾥引⼊著名的尾递归-斐波那契数列的实现。
既然涉及到底层,⾃然就该⽤C语⾔实现。
int Fib(int n){if(i==1||i==2)return 1;return Fib(i-1)+Fib(i-2);}我们不妨把函数Fib和return语句中调⽤的函数看作是不同的函数(只是具有了相同的名称),那么就涉及到了函数调⽤的知识,我们知道,在函数调⽤的过程中(⽐如A函数中调⽤了B函数),编译器就会把A函数的参数,局部变量及返回地址压⼊栈中存储,再进⾏B函数的调⽤。
这⾥⽤汇编的思想解释会⽐较⽣动,如下图所⽰,假设传⼊参数为5。
为⽅便理解,绘图会⽐较⽣动,如下图所⽰,假设传⼊参数为5此时返回值已有确定值,开始按顺序出栈,运⾏到有返回地址字样时执⾏命令call XXXX(跳⼊该函数体内执⾏该函数),如下图运⾏到这⾥跳⼊Fib(3)函数体内,我们不妨进⼊函数体内看看,int Fib(int n) <---n=3{if(i==1||i==2) <---跳过return 1;return Fib(i-1)+Fib(i-2); //运⾏到此处,由于Fib(2)已经由上⼀步得出,即此时语句等同于return 1+Fib(1); }操作同第⼀步下⼀步,出栈,由于Fib(3)的值已经明确,继续出栈继续出栈步⼊Fib(4)函数体内,同理运⾏到return 2+Fib(2)语句,调⽤函数Fib(2),出栈......值已明确,继续出栈步⼊函数Fib(5),运⾏⾄return 3+Fib(3)语句处,调⽤函数Fib(3)同理步⼊Fib(3)函数,运⾏⾄return 1+Fib(1)语句处,调⽤函数Fib(1),进⽽出栈,ebx更新为2,继续出栈Fib(5)已有确定值,出栈,此时栈空,即Fib(5)等于5.到这⾥我们就可以⽐较直观看出递归及函数调⽤过程中与栈的关系了,软件漏洞与技术⼀书上也描述的很详细,在这⾥贴出来。
c语言函数参数传递方式三种
c语言函数参数传递方式有三种,它们将在下文中进行具体探讨。
首先,谈到c语言函数参数传递方式,按值传递是最常用的方式之一。
在按值传递中,函数参数实参向形参传递的内容是一个复制品,即实
参对应的副本,而形参对实参不产生任何影响。
也就是说,在按值传
递中,实参中的值并不会发生变化,而所有的改变都发生在副本上,
这就是按值传递的本质。
其次,按地址传递也是常用的一种c语言函数参数传递方式。
在按地
址传递中,函数参数实参将地址值(而不是具体的值)传递给形参,
因此,函数内部的代码可以直接读取实参的值,如果修改了实参的值,实参的值也会被修改。
最后,按引用传递是一种特殊的按地址传递,但是其使用方式却与按
地址传递不同。
在按引用传递中,实参只需要传递一个“对对象的引
用(reference)”即可,因此,函数内已经可以获取到实参的地址,
并且可以直接读取或修改实参的值,而不用把地址传递给形参。
总结以上,c语言函数参数传递方式分为三种:按值传递、按地址传递和按引用传递,这三种传递方式在某些情况下表现出不同的特点,所
以在实际开发中,根据具体需求来选择最合适的函数参数传递方式,
才能更好地完成开发任务。
C函数参数和返回值三种传递方式值传递指针传递和引用传递函数参数和返回值的传递方式可以分为三种:值传递、指针传递和引用传递。
这三种传递方式在实际应用中各有优劣,需要根据具体的情况选择合适的方式。
下面将详细介绍这三种传递方式。
值传递是最简单、最直接的参数传递方式。
它将参数的值复制给形参,在函数内部对形参的修改不会影响到实参。
值传递通常用于传递基本数据类型,例如整型、浮点型、字符型等。
在函数调用过程中,实参的值被复制到形参中,形参的修改不会对实参产生影响。
这样的传递方式可以保证函数内部的操作不会改变外部数据,使得程序更加可靠。
但是,通过值传递传递大型或复杂的数据结构时会产生较大的开销,因为需要复制整个数据结构。
此外,对于递归或大量数据的处理,使用值传递会占用较多的内存空间,影响程序的性能。
指针传递是将参数的地址传递给形参,形参通过指针访问实参的值。
使用指针传递可以在函数内部修改实参的值。
指针传递常用于需要函数内部直接修改实参值的情况,例如交换两个变量的值。
在函数调用过程中,实参变量的地址被传递给对应的指针形参,函数内部通过指针访问实参的值。
指针传递相对于值传递来说,在内存使用上更加高效,因为只需要传递地址,并不需要复制整个数据结构。
但是,指针传递需要注意指针的空指针和野指针问题,以及对指针所指向的内存进行正确的管理和释放。
引用传递是C++中特有的传递方式,它将实参的别名传递给形参,形参和实参指向同一块内存地址。
使用引用传递可以在函数内部直接修改实参的值,并且不会引入额外的内存开销。
引用传递通常用于传递复杂数据类型,例如数组和结构体等。
在函数调用过程中,实参变量的别名被传递给对应的引用形参,函数内部对引用形参的修改直接作用于实参,从而避免了复制数据结构的开销。
引用传递在使用上更加简洁,代码可读性更高。
但是,需要注意引用的生命周期和作用域,以避免引用失效或引发访问非法内存的问题。
从性能的角度来看,值传递和引用传递相对较为高效,因为不需要额外的内存开销。
C语言调用函数过程详解Sunny.man1.使用环境:gcc 版本4.1.2 20071124 (Red Hat 4.1.2-42)2.示例源代码int foo(int a,int b){int a1=0x123;return a1+a+b;}int main(){foo(2,3);return 0;}3.运行程序命令:gdb a.outStartDisassemble4.汇编函数清单4.1main函数的汇编0x0804836c <main+0>: lea 0x4(%esp),%ecx0x08048370 <main+4>: and $0xfffffff0,%esp0x08048373 <main+7>: pushl 0xfffffffc(%ecx)0x08048376 <main+10>: push %ebp0x08048377 <main+11>: mov %esp,%ebp0x08048379 <main+13>: push %ecx0x0804837a <main+14>: sub $0x8,%esp0x0804837d <main+17>: movl $0x3,0x4(%esp)0x08048385 <main+25>: movl $0x2,(%esp)0x0804838c <main+32>: call 0x8048354 <foo>0x08048391 <main+37>: mov $0x0,%eax0x08048396 <main+42>: add $0x8,%esp0x08048399 <main+45>: pop %ecx0x0804839a <main+46>: pop %ebp0x0804839b <main+47>: lea 0xfffffffc(%ecx),%esp0x0804839e <main+50>: ret4.2Foo函数的汇编0x08048354 <foo+0>: push %ebp0x08048355 <foo+1>: mov %esp,%ebp0x08048357 <foo+3>: sub $0x10,%esp0x0804835a <foo+6>: movl $0x123,0xfffffffc(%ebp)0x08048361 <foo+13>: mov 0x8(%ebp),%eax0x08048364 <foo+16>: add 0xfffffffc(%ebp),%eax0x08048367 <foo+19>: add 0xc(%ebp),%eax0x0804836a <foo+22>: leave0x0804836b <foo+23>: ret5.程序执行时分析(gdb) info registerseax 0xbf820894 -1081997164ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207ec 0xbf8207ecebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x804837d 0x804837d <main+17>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51注:此时已经执行到main的第14行下一条指令是movl $0x3,0x4(%esp)此时的esp是0xbf8207ec。
5.1分析esp=0xbf8207ec的来历(gdb) x/20 $esp0xbf8207ec: 0x0056eff4 0x004205d0 0xbf820810 0xbf8208680xbf8207fc: 0x00445dec 0x0042cca0 0x080483b0 0xbf8208680xbf82080c: 0x00445dec 0x00000001 0xbf820894 0xbf82089c0xbf82081c: 0x0042d810 0x00000000 0x00000001 0x000000010xbf82082c: 0x00000000 0x0056eff4 0x0042cca0 0x000000005.1.1Main+140x0804837a <main+14>: sub $0x8,%esp现在的esp=0xbf8207ec ebp=0xbf8207f8 eip=0x804837d 则原来esp的值就是0xbf8207ec+0x8=0xbf8207f45.1.2 Main+130x08048379 <main+13>: push %ecxEsp=0xbf8207f4+0x04=0xbf8207f85.1.3 Main+110x08048377 <main+11>: mov %esp,%ebpEbp=0xbf8207f8 和现在的EBP值是一样的。
5.1.4 Main+100x08048376 <main+10>: push %ebpEsp=0xbf8207f8+4=0xBF8207FC5.1.5 Main+70x08048373 <main+7>: pushl 0xfffffffc(%ecx)ESP的值= 0xBF8207FC+4=0xBF8208005.1.6 Main+40x08048370 <main+4>: and $0xfffffff0,%esp从这句可以看出esp的值是0xBF82080X,无法进一步得出了。
不过没有关系,我们可以通过上一句来看出.5.1.7 Main+00x0804836c <main+0>: lea 0x4(%esp),%ecx这个说明现在的ecx寄存器中,存着原来Esp的值+4,那我们知道ecx里存的是0xbf820810。
这个值是最初的esp+4那最初的esp就是0xbf82080c。
5.2从main+0开始进行推导1.最早的esp=0xbf82080c ebp=0xbf8207f82.现在的各地址的值是:0xbf8207ec: 0x0056eff4 0x004205d0 0xbf820810 0xbf8208680xbf8207fc: 0x00445dec 0x0042cca0 0x080483b0 0xbf8208680xbf82080c: 0x00445dec 0x00000001 0xbf820894 0xbf82089c0xbf82081c: 0x0042d810 0x00000000 0x00000001 0x000000010xbf82082c: 0x00000000 0x0056eff4 0x0042cca0 0x000000003.0x0804836c <main+0>: lea 0x4(%esp),%ecx4.0x08048370 <main+4>: and $0xfffffff0,%espesp=0xbf82080c & 0xFFFF FFF0=0xbf8208005.0x08048373 <main+7>: pushl 0xfffffffc(%ecx)esp=0xbf820800-4=0xbf8207fc ecx所指向的地值-4也就是原来esp(0xbf82080c )所指向的值0x00445dec6.0x08048376 <main+10>: push %ebpesp=0xbf8207fc -4=0xbf8207f8 原来的ebp内容=0xbf8208687.0x08048377 <main+11>: mov %esp,%ebpEbp=esp=0xbf8207f88.0x08048379 <main+13>: push %ecxEsp=0xbf8207f8=4=0xbf8207f4 里面的内容: 0xbf820810 来缘是原来的esp(0xbf82080c)+49.0x0804837a <main+14>: sub $0x8,%espEsp=0xbf8207f4-8=0xbf8207ec10.0x0804837d <main+17>: movl $0x3,0x4(%esp)11.0x08048385 <main+25>: movl $0x2,(%esp)12.0x0804838c <main+32>: call 0x8048354 <foo>Call把下一个指令的地址压栈,并esp-4同时改写eipeax 0xbf820894 -1081997164ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207e8 0xbf8207e8ebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x8048354 0x8048354 <foo>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51esp=0xbf8207ec-4=0xbf8207e8 内容: 0x08048391这个值正式一下条指令Main+37的地址值。
0x08048391 <main+37>: mov $0x0,%eax(gdb)x/20 $esp0xbf8207e8: 0x08048391 0x00000002 0x00000003 0xbf8208100xbf8207f8: 0xbf820868 0x00445dec 0x0042cca0 0x080483b00xbf820808: 0xbf820868 0x00445dec 0x00000001 0xbf8208940xbf820818: 0xbf82089c 0x0042d810 0x00000000 0x000000010xbf820828: 0x00000001 0x00000000 0x0056eff4 0x0042cca013.0x08048354 <foo+0>: push %ebpEsp=esp-4=0xbf8207e4(gdb) info registerseax 0xbf820894 -1081997164ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207e4 0xbf8207e4ebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x8048355 0x8048355 <foo+1>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5114.0x08048355 <foo+1>: mov %esp,%ebpEbp=esp(gdb) info registerseax 0xbf820894 -1081997164ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207e4 0xbf8207e4ebp 0xbf8207e4 0xbf8207e4esi 0x42cca0 4377760edi 0x0 0eip 0x8048357 0x8048357 <foo+3>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51(gdb) x/20 $esp0xbf8207e4: 0xbf8207f8 0x08048391 0x00000002 0x00000003 0xbf8207f4: 0xbf820810 0xbf820868 0x00445dec 0x0042cca0 0xbf820804: 0x080483b0 0xbf820868 0x00445dec 0x00000001 0xbf820814: 0xbf820894 0xbf82089c 0x0042d810 0x00000000 0xbf820824: 0x00000001 0x00000001 0x00000000 0x0056eff415.0x08048357 <foo+3>: sub $0x10,%espEsp=esp-0x10=0xbf8207e4-0x10=0xbf8207d416.0x0804835a <foo+6>: movl $0x123,0xfffffffc(%ebp) 0xfffffffc(%ebp)=0xbf8207e4-4=0xbf8207e0(gdb) info registerseax 0xbf820894 -1081997164ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207d4 0xbf8207d4ebp 0xbf8207e4 0xbf8207e4esi 0x42cca0 4377760edi 0x0 0eip 0x804835a 0x804835a <foo+6>eflags 0x200286 [ PF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51(gdb) x/20 $esp0xbf8207d4: 0x0056d210 0xbf820808 0x080483c9 0x00000123 0xbf8207e4: 0xbf8207f8 0x08048391 0x00000002 0x00000003 0xbf8207f4: 0xbf820810 0xbf820868 0x00445dec0x0042cca0 0xbf820804: 0x080483b0 0xbf820868 0x00445dec 0x00000001 0xbf820814: 0xbf820894 0xbf82089c 0x0042d810 0x00000000 Esp=0xbf8207d4 Ebp-4=0x123 Ebp=0xbf8207e417.0x08048361 <foo+13>: mov 0x8(%ebp),%eax18.0x08048364 <foo+16>: add 0xfffffffc(%ebp),%eax19.0x08048367 <foo+19>: add 0xc(%ebp),%eaxEbp+8是传来的参数ebp-4是第一个临时变量20.0x0804836a <foo+22>: leaveLeave等同于下列两句Move $ebp,$esppop %ebp把esp=ebp= 0xbf8207e4ebp=0xbf8207f8esp=esp+4=0xbf8207e8(gdb)info registerseax 0x128 296ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207e8 0xbf8207e8ebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x804836b 0x804836b <foo+23>eflags 0x200206 [ PF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5121.0x0804836b <foo+23>: retpop %eipesp=esp+4=0xbf8207ec(gdb)info regeax 0x128 296ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207ec 0xbf8207ecebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x8048391 0x8048391 <main+37>eflags 0x200206 [ PF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5122.0x08048396 <main+42>: add $0x8,%esp esp=esp+8=0xbf8207ec+8=0xbf8207f4(gdb) info registerseax 0x0 0ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207f4 0xbf8207f4ebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x8048399 0x8048399 <main+45>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51(gdb) x/20 $esp0xbf8207f4: 0xbf820810 0xbf820868 0x00445dec 0x0042cca0 0xbf820804: 0x080483b0 0xbf820868 0x00445dec 0x00000001 0xbf820814: 0xbf820894 0xbf82089c 0x0042d810 0x00000000 0xbf820824: 0x00000001 0x00000001 0x00000000 0x0056eff4 0xbf820834: 0x0042cca0 0x00000000 0xbf820868 0x7f1f8fc023.0x08048399 <main+45>: pop %ecxesp=esp+4=0xbf8207f8(gdb) info registerseax 0x0 0ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207f8 0xbf8207f8ebp 0xbf8207f8 0xbf8207f8esi 0x42cca0 4377760edi 0x0 0eip 0x804839a 0x804839a <main+46>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5124.0x0804839a <main+46>: pop %ebpesp=esp+4=0xbf8207fcebp=0xbf820868(gdb) info registerseax 0x0 0ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf8207fc 0xbf8207fcebp 0xbf820868 0xbf820868esi 0x42cca0 4377760edi 0x0 0eip 0x804839b 0x804839b <main+47>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5125.0x0804839b <main+47>: lea 0xfffffffc(%ecx),%espEsp=0xbf820810 -4=0xbf82080c 恢复到原来的esp 26.0x0804839e <main+50>: ret等同于pop %eipEsp=esp+4=0xbf820810(gdb) info registerseax 0x0 0ecx 0xbf820810 -1081997296edx 0x1 1ebx 0x56eff4 5697524esp 0xbf820810 0xbf820810ebp 0xbf820868 0xbf820868esi 0x42cca0 4377760edi 0x0 0eip 0x445dec 0x445dec <__libc_start_main+220>eflags 0x200292 [ AF SF IF ID ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 5127.mov %eax,(%esp)把0做为返回值传给libc_start_main0x00445de9 <__libc_start_main+217>: call *0x8(%ebp) 0x00445dec <__libc_start_main+220>: mov %eax,(%esp)。