
程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。
不同处理器和编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。
寄存器是处理器加工数据或运行程序的重要载体,用于存放程序执行中用到的数据和指令。因此函数调用栈的实现与处理器寄存器组密切相关。
AX(AH、AL):累加器。有些指令约定以AX(或AL)为源或目的寄存器。输入/输出指令必须通过AX或AL实现,例如:端口地址为43H的内容读入CPU的指令为INAL,43H或INAX,43H。目的 *** 作数只能是AL/AX,而不能是其他的寄存器。 [5]
BX(BH、BL): 基址寄存器 。BX可用作间接寻址的地址寄存器和 基地址寄存器 ,BH、BL可用作8位通用数据寄存器。 [5]
CX(CH、CL):计数寄存器。CX在循环和串 *** 作中充当计数器,指令执行后CX内容自动修改,因此称为计数寄存器。 [5]
DX(DH、DL):数据寄存器。除用作通用寄存器外,在 I/O指令 中可用作端口 地址寄存器 ,乘除指令中用作辅助累加器。 [5]
2指针和 变址寄存器
BP( Base Pointer Register):基址指针寄存器。 [5]
SP( Stack Pointer Register): 堆栈指针寄存器 。 [5]
SI( Source Index Register):源变址寄存器。 [5]
DI( Destination Index Register):目的变址寄存器。 [5]
函数调用栈的典型内存布局如下图所示:
图中给出主调函数(caller)和被调函数(callee)的栈帧布局,"m(%ebp)"表示以EBP为基地址、偏移量为m字节的内存空间(中的内容)。该图基于两个假设:第一,函数返回值不是结构体或联合体,否则第一个参数将位于"12(%ebp)" 处;第二,每个参数都是4字节大小(栈的粒度为4字节)。在本文后续章节将就参数的传递和大小问题做进一步的探讨。 此外,函数可以没有参数和局部变量,故图中“Argument(参数)”和“Local Variable(局部变量)”不是函数栈帧结构的必需部分。
其中,主调函数将参数按照调用约定依次入栈(图中为从右到左),然后将指令指针EIP入栈以保存主调函数的返回地址(下一条待执行指令的地址)。进入被调函数时,被调函数将主调函数的帧基指针EBP入栈,并将主调函数的栈顶指针ESP值赋给被调函数的EBP(作为被调函数的栈底),接着改变ESP值来为函数局部变量预留空间。此时被调函数帧基指针指向被调函数的栈底。以该地址为基准,向上(栈底方向)可获取主调函数的返回地址、参数值,向下(栈顶方向)能获取被调函数的局部变量值,而该地址处又存放着上一层主调函数的帧基指针值。本级调用结束后,将EBP指针值赋给ESP,使ESP再次指向被调函数栈底以释放局部变量;再将已压栈的主调函数帧基指针d出到EBP,并d出返回地址到EIP。ESP继续上移越过参数,最终回到函数调用前的状态,即恢复原来主调函数的栈帧。如此递归便形成函数调用栈。
EBP指针在当前函数运行过程中(未调用其他函数时)保持不变。在函数调用前,ESP指针指向栈顶地址,也是栈底地址。在函数完成现场保护之类的初始化工作后,ESP会始终指向当前函数栈帧的栈顶,此时,若
71 库函数的正确调用
1C语言提供了丰富的库函数,包括常用数学函数、对字符和字符串处理函数、输入输出处理函数等。在调用库函数时要注意以下几点:
(1)调用C语言标准库函数时必须在源程序中用include命令,include命令的格式是:
#include″头文件名″
include命令必须以#号开头,系统提供的头文件名都以h作为后缀,头文件名用一对双引号″″或一对尖括号〈〉括起来。
(2)标准库函数的调用形式:
函数名(参数表)
2在C语言中库函数的调用可以以两种形式出现:出现在表达式中;作为独立的语句完成某种 *** 作。
72 函数的定义方法
1C语言函数的一般形式为:
函数返回值的类型名 函数名(类型名 形参1,类型名 形参2,…)
{
说明部分
语句部分
}
定义的第一行是函数的首部,{}中的是函数体。
2在老的C语言版本中,函数的首部用以下形式:
函数返回值的类型名 函数名(形参1,形参2…)
形参类型说明;
新的ANSI标准C兼容这种形式的函数首部说明。
3函数名和形参名是由用户命名的标识符。在同一程序中,函数名必须。形式参数名只要在同一函数中即可,可以与函数中的变量同名。
4C语言规定不能在一个函数内部再定义函数。
5若在函数的首部省略了函数返回值的类型名,把函数的首部写成:
函数名(类型名 形参1,类型名 形参2,…)
则C默认函数返回值的类型为int类型。
6当没有形参时,函数名后面的一对圆括号不能省略。
73 函数的类型和返回值
1函数的类型由函数定义中的函数返回值的类型名确定,函数的类型可以是任何简单类型,如整型、字符型、指针型、双精度型等,它指出了函数返回值的具体类型。当函数返回的是整型值时,可以省略函数类型名。当函数只完成特定的 *** 作而没有或不需要返回值时,可用类型名void(空类型)。
2函数返回值就是return语句中表达式的值。当程序执行到return语句时,程序的流程就返回到调用该函数的地方(通常称为退出调用函数),并带回函数值。
74 形式参数与实际参数,参数值的传递
1在函数定义中,出现的参数名称为形参(形式参数),在调用函数时,使用的参数值称为实参(实际参数)。
2调用函数和被调用函数之间的参数值的传递是″按值″进行的,即数据只能从实参单向传递给形参。也就是说,当简单变量作为实参时,用户不能在函数中改变对应实参的值。
75 函数的正确调用(嵌套调用,递归调用)
1调用函数时,函数名必须与被调用的函数名字完全一样。实参的个数与类型和形参的个数与类型一致。
2C语言规定:函数必须先定义,后调用,也就是被调用函数必须在调用之前加以说明,或被调用函数整个放在调用函数之前。但对返回值类型为int或char类型的函数可以放在调用函数的后面。
3C语言中函数定义都是互相平行、独立的,C语言不允许嵌套定义函数,但允许嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另一个函数。
4在C程序中,调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。
5递归调用函数是C语言的特点之一,有时递归调用会使求解的问题变得更简单明了。
76 局部变量和全局变量
1局部变量
在一函数内部所定义的变量称为局部变量,局部变量只在本函数范围内有效。
注意:
①不同函数可以使用相同的局部变量名,它们将代表不同的对象,互不干扰;
②一个函数的形参也为局部变量;
③在函数内部,复合语句也可定义变量,这些变量也为局部变量,只在此复合语句中有效。
2全局变量
在C语言中,程序的编译单位是源程序文件,一个源程序文件中包含一个或多个函数。在函数之外所定义的变量称为外部变量,也称为全局变量。全局变量可以被包含它的源程序文件中的其他函数所共用,作用域为从定义变量的位置开始到源程序文件结束,全局变量可以增加函数之间数据的联系。
注意:当在同一个源程序文件中,全局变量与局部变量同名时,在局部变量的作用范围内,全局变量不起作用,局部变量起作用。
77 变量的存储类别、作用域及生存期
1变量的存储类别
在C语言中,有两类存储类别:自动类别及静态类别。
有4个与两种存储类别有关的说明符:auto(自动)、register(寄存器)、static(静态)和extern(外部),这些说明符一般与类型说明一起出现,一般放在类型名的左边,例如:
auto long I,j;
也可写成:
long auto I,j;
(1)自动变量:自动变量是C程序中使用最多的一种变量,这种变量的建立和撤消都是在系统中自动进行的。
格式:[auto]数据类型 变量名[=初始化表达式],…;
上面的说明格式中,方括号中是可省略的部分,auto为自动类别标识符,若省略auto,系统缺省的存储类别也为自动类别。
注意:函数的形参也为自动类别,在定义时不必加存储类别标识符。
(2)寄存器变量:寄存器变量与自动变量的性质相同,其区别只在于存储的位置不同,寄存器变量存储在CPU的寄存器中,而自动变量存储在内存中的动态存储区,寄存器变量的存取速度要快些。
格式:register数据类型,变量名[=初始化表达式],…;
上面的说明格式中,register为寄存器变量的存储类别标识符。
说明:
①CPU中寄存器的数目是有限的,因此只能把少数的变量说明为寄存器变量;
②寄存器变量是存放在寄存器中的,而不是存放于内存中,所以,寄存器变量无地址;
③寄存器变量的说明应尽量放在靠近要使用的地方,用完后尽快释放,这样可提高使用效率。
(3)静态变量:静态类别变量的存储空间在程序的整个运行期间是固定的。
格式:static数据类型 变量名[=初始化表达式],…;
在上面的说明格式中,static为静态变量的存储类别标识符。
静态变量的初始化在编译时进行,定义时可用常量或表达式进行显式初始化。对于没有初始化的静态变量,自动初始化为0(整型)或00(实型)。
注意:静态变量具有可继承性,这与自动变量有所不同。
(4)外部变量:使用extern可使外部变量使用范围扩充到需要使用它的函数。外部变量可作显式的初始化,若不作初始化,系统将自动地初始化为0或00。
格式:[extern]数据类型,变量名[=初始化表达式],…;
上面的说明格式中,extern使外部变量的作用范围扩大到其他源程序文件中。
注意:局部变量既可以说明为自动类别,也可以说明为静态类别;全局变量只能说明为静态类别。
2变量的作用域及生存期
在C语言中,变量必须先说明后使用,在程序中一个已定义的变量的使用范围就是此变量的作用域。经过赋值的变量在程序运行期间能保持其值的时间范围为该变量的生存期。
(1)局部变量的使用域及生存期
①自动变量的使用域及生存期
自动变量的存储单元被分配在内存的动态存储区,每当进函数体(或复合语句)时,系统自动为自动变量分配存储单元,退出时自动释放这些存储单元。自动变量的作用域为从定义的位置起,到函数体(或复合语句)结束为止。
自动变量在进入到定义它们的函数体(或复合语句)时生成,在退出所在的函数体(或复合语句)时消失,这就是自动变量的生存期。
使用自动变量的优点是使各函数之间造成信息分隔,不同函数中使用同名变量时不会相互影响。
②寄存器变量的使用域及生存期
寄存器变量的使用域及生存期与自动变量相同。
③静态存储类别的局部变量
在函数体(或复合语句)内部,用static说明的变量静态存储类别的局部变量,这种变量的作用域与自动(或寄存器)变量的作用域相同,但是生存期有所不同。
在整个程序运行期间,静态局部变量在内存的静态存储区中占据着永久的存储单元,甚至在退出函数后下次再进入函数时,静态局部变量仍使用原来的存储单元。由于不释放存储单元,所以这些存储单元中的值将会被保留下来。静态局部变量的生存期将一直延长到程序运行结束。
静态局部变量适合于在函数调用之间必须保留局部变量值的独立变量。
对于C语言中的函数类型,一般可以分为以下两类:
1 库函数(Library Function):也称为内置函数(Built-in Function),是由C语言提供的、已经封装好的函数。库函数通常具有标准化、通用化的特点,包括数学运算、字符串处理、文件 *** 作等方面。例如`printf()`和`scanf()`是C语言中常用的库函数。
2 用户自定义函数(User-Defined Function):也称为外置函数(External Function),是程序员根据需求自行编写的函数。用户自定义函数可以将某一段需要重复使用的代码封装成一个函数,在其他地方调用该函数即可实现相同的功能,起到了复用代码的作用。在需要多次执行特定任务时,使用自定义函数可以使程序结构更加清晰、易于理解。
最简单的理解,函数就是一个子程序
或者说是程序的模块、零件
把一些代码封装起来,给他们起个名字(函数名)
到时候要用到这些代码的时候,引用用他们的名字就可以了
所谓的函数的参数,就是引用这些代码模块的时候,需要这些模块处理的数据;而函数的返回值,就是处理的结果。
C是面向过程的语言,函数是组成C程序的基本单元。
例如,我定义一个函数
int Add(int i)
{
return i+1;
}
其功能是,取得i的值,返回i+1的值
我在主程序中就可以这样调用这个函数
int main(void)
{
int a=1;
a=Add(a);
printf("%d",a);
return 0;
}
程序执行的结果就是2;
同样,我要是定义这样一个无参函数
void foo(void)
{
printf("I am a function");
}
其功能是打印一行字
那么这样的主程序:
int main(void)
{
foo();
return 0;
}
其执行结果就是一行"I am a function"
不知说明白没,呵呵,还有不解可以提出,我的qq226527085
函数是一个独立的程序段,它执行具体的,明确的任务。也就是说函数是我们预先编写好的一段处理某个具体问题的代码。这样复杂的问题就会变得简单~~
C语言中我们通常会使用函数来执行一系列指令。一次函数调用将在一个程序内的任何一个给定点上执行一系列指令。函数可以根据需要被多次调用。如果一个程序中会多次的执行同一个任务,那么函数的使用将会大大的减少程序的代码量,而且包含函数的程序还便于维护和修改程序,因为我们只需要修改函数就可以对程序中的多处进行修改了。
函数有时有返回值 有时可以没有返回值(只做一些 *** 作 不返回)
你问题具体点就更好回答了
改成如下:
#include<stdioh>
float circle(int r1)
{
float area1=3r1r1;
return area1;
}
float circle(int r2)
{
float area2=3r2r2;
return area2;
}
main()
{
printf("%f",circle(1)+circle(2));
}
错误分析:
1、首先调用函数时括号里应该是实际的数值,或者是已经被赋值的变量。
2、C语言函数体声明时的参数不能直接赋值,这个声明只是告诉编译器我这个函数要接受这几个、这个类型的参数。
以上就是关于C语言函数调用栈全部的内容,包括:C语言函数调用栈、2017年计算机二级C语言考点解析:函数、C语言中函数的类型有哪些呢等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)