怎么调用计算机底层函数,函数调用

本文详细介绍了函数调用的过程,包括无参函数调用、实际参数表的使用、函数表达式、函数语句和函数实参。讨论了C语言中函数调用的一般形式,以及用户空间和内核空间的函数调用。还提到了函数调用的求值顺序问题,以及在函数调用中可能出现的嵌套调用。此外,文中还阐述了函数调用的参数传递方式,包括实参和形参的关系,并举例说明了函数调用的实际实现。最后,通过函数调用实例解释了栈指针和帧指针在调用过程中的变化。
摘要由CSDN通过智能技术生成

计算机编译或运行时,使用某个函数来完成相关命令。对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数、变量或其它构造类型数据及表达式。各实参之间用逗号分隔。

中文名

函数调用

外文名

function reference一般形式

在程序中通过对函数的调用来

包括内容

函数表达式

嵌套调用

C语言中不允许作嵌套的函数定义

函数调用一般形式

语音

在程序中通过对函数的调用来执行函数体,其过程与其它语言的子程序调用相似。

C语言中,函数调用的一般形式为:

函数名(实际参数表)

对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数、变量或其它构造类型数据及表达式。各实参之间用逗号分隔。[1]

函数调用用户空间(用户态)和内核空间(内核态)

语音

操作系统的进程空间可分为用户空间和内核空间,它们需要不同的执行权限。其中函数调用运行在用户空间。[1]

函数调用包括内容

语音

函数调用函数表达式

函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如:z=max(x,y)是一个赋值表达式,把max的返回值赋予变量z。[1]

函数调用函数语句

函数调用的一般形式加上分号即构成函数语句。例如: printf ("%d",a);scanf ("%d",&b);都是以函数语句的方式调用函数。[1]

函数调用函数实参

函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。例如: printf("%d",max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。对此,各系统的规定不一定相同。介绍printf 函数时已提到过,这里从函数调用的角度再强调一下。[1]

【例】

main()

{int i=8;printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);}

如按照从右至左的顺序求值。运行结果应为:

8

7

7

8

如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为:

9

8

8

9

应特别注意的是,无论是从左至右求值, 还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值,所以结果为8,7,7,8。上述问题如还不理解,上机一试就明白了。[1]

被调用函数的声明和函数原型

在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。[1]

其一般形式为:

类型说明符 被调函数名(类型 形参,类型 形参…);

或为:

类型说明符 被调函数名(类型,类型…);

括号内给出了形参的类型和形参名,或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。

例main函数中对max函数的说明为:

int max(int a,int b);

或写为:

int max(int,int);

C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。[1]

1) 如果被调函数的返回值是整型或字符型时,可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。例8.2的主函数中未对函数s作说明而直接调用即属此种情形。[1]

2) 当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作说明而直接调用。例如例8.1中,函数max的定义放在main 函数之前,因此可在main函数中省去对max函数的函数说明int max(int a,int b)。[1]

3) 如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如:

char str(int a);

float f(float b);

main()

{……}

char str(int a)

{……)

float f(float b)

{……}

其中第一,二行对str函数和f函数预先作了说明。因此在以后各函数中无须对str和f函数再作说明就可直接调用。[1]

4) 对库函数的调用不需要再作说明,但必须把该函数的头文件用include命令包含在源文件前部。[1]

函数调用嵌套调用

语音

C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图。[1]

图表示了两层嵌套的情形。其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a函数执行完毕返回main函数的断点继续执行。[1]

【例】计算s=2∧2!+3∧2!

本题可编写两个函数,一个是用来计算平方值的函数f1,另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值,再在f1中以平方值为实参,调用 f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。[1]

long f1(int p)

{int k;

long r;

long f2(int);

k=p*p;

r=f2(k);

return r;}

long f2(int q)

{long c=1;

int i;

for(i=1;i<=q;i++)

c=c*i;

return c;}

main()

{int i;

long s=0;

for (i=2;i<=3;i++)

s=s+f1(i);

printf("\ns=%ld\n",s);}

在程序中,函数f1和f2均为长整型,都在主函数之前定义,故不必再在主函数中对f1和f2加以说明。在主程序中,执行循环程序依次把i值作为实参调用函数f1求i2值。在f1中又发生对函数f2的调用,这时是把i2的值作为实参去调f2,在f2 中完成求i2!的计算。f2执行完毕把C值(即i2!)返回给f1,再由f1返回主函数实现累加。至此,由函数的嵌套调用实现了题目的要求。由于数值很大,所以函数和一些变量的类型都说明为长整型,否则会造成计算错误。[1]

函数调用实际实现

语音

函数调用指针寄存器

EBP

EBP是所谓的帧指针,指向当前活动记录的上方(上一个活动记录的最下方)

ESP

ESP是所谓的栈指针,指向当前活动记录的最下方(下一个将要插入的活动记录的最上方)

这两个指针的值规定了当前活动记录的位置[1]

函数调用参数传递

将函数参数压栈:mov eax,dword ptr [n] ;(n为参数变元)

push eax[1]

函数调用操作

函数调用将执行如下操作:

⒈将帧指针压入栈中:push ebp

⒉使得帧指针等于栈指针:mov ebp,esp

⒊使栈指针自减,自减得到的内存地址应当能够(足够)用来存储被调用函数的本地状态:sub esp,0CCh

注意:0CCh为0xCC,随着具体函数的不同而不同。

函数调用传入保存状态

push ebx ;保存ebx寄存器的值

push esi ;保存esi寄存器的值

push edi ;保存edi寄存器的值[1]

函数调用装入edi

lea edi,[ebp-0CCh] ;0cch是当前活动记录的大小。

EDI是目的变址寄存器。[1]

函数调用恢复传入的保存状态

00411417 pop edi

00411418 pop esi

pop ebx[1]

函数调用栈指针上移,恢复空间

add esp,0CCh

函数调用函数返回释放空间

当函数返回时,编译器和硬件将执行如下操作:

⒈使栈指针等于帧指针: mov esp,ebp[1]

⒉从栈中将旧的帧指针弹出: pop ebp[1]

⒊返回:ret[1]

函数调用实例一

;void function(int n);{push ebp

mov ebp,esp

sub esp,0CCh

push ebx

push esi

push edi

lea edi,[ebp-0CCh]

mov ecx,33h

mov eax,0CCCCCCCCh

rep stos dword ptr es:[edi]

;char a=1;

mov byte ptr [a],1

;if(n==0)return;

cmp dword ptr [n],0

jne function+2Ah (4113CAh)

jmp function+77h (411417h)

;printf("%d\t(0x%08x)\n",n,&n);

mov esi,esp

lea eax,[n]

push eax

mov ecx,dword ptr [n]

push ecx

push offset string "%d\t(0x%08x)\n" (415750h)

call dword ptr [__imp__printf (4182B8h)]

add esp,0Ch

cmp esi,esp

call @ILT+305(__RTC_CheckEsp) (411136h)

;function(n-1);

mov eax,dword ptr [n]

sub eax,1

push eax

call function (411041h)

add esp,4

;printf("----%d\t(0x%08x)\n",n,&n);

mov esi,esp

lea eax,[n]

push eax

mov ecx,dword ptr [n]

push ecx

push offset string "----%d\t(0x%08x)\n" (41573Ch)

call dword ptr [__imp__printf (4182B8h)]

add esp,0Ch

cmp esi,esp

call @ILT+305(__RTC_CheckEsp) (411136h);}

pop edi

pop esi

pop ebx

add esp,0CCh

cmp ebp,esp

call @ILT+305(__RTC_CheckEsp) (411136h)

mov esp,ebp

pop ebp

ret[1]

函数调用实例二

117: bR = t1(p);

汇编代码如下:

00401FB8 mov ecx,dword ptr [ebp-8] ;将参数放入ecx寄存器

00401FBB push ecx ;参数入栈

00401FBC call @ILT+10(t1) (0040100f) ;函数调用,下一行地址00401FC1入栈

00401FC1 add esp,4 ;函数返回,堆栈指针加4,复原为00401FB8时的值

00401FC4 mov dword ptr [ebp-10h],eax ;从eax中取出高级语言中的函数返回值,放入bR变量中

其中t1函数如下:

125: BOOL t1(void* p)

126: {

00402030 push ebp ;ebp入栈

00402031 mov ebp,esp ;ebp指向此时堆栈的栈顶

00402033 sub esp,44h ;esp减少一个值,空出一段存储区

00402036 push ebx ;将三个寄存器的值入栈,以便在函数中使用它

00402037 push esi ;

00402038 push edi ;

00402039 lea edi,[ebp-44h] ;

0040203C mov ecx,11h ;

00402041 mov eax,0CCCCCCCCh ;

00402046 rep stos dword ptr [edi] ;

127: int* q = (int*)p; ;

00402048 mov eax,dword ptr [ebp+8] ;ebp+8指向函数输入参数的最低位地址;

;如果是ebp+4则指向函数返回地址00401FC1的最低位,值为C1[1]

0040204B mov dword ptr [ebp-4],eax ;

128: return 0;

0040204E xor eax,eax ;返回值放入eax寄存器中

129: }

00402050 pop edi ;三个寄存器出栈

00402051 pop esi ;

00402052 pop ebx ;

00402053 mov esp,ebp ;esp复原

00402055 pop ebp ;ebp出栈,它的值也复原了

00402056 ret ;返回到此时栈顶存储的代码地址:00401FC1

;故而如果不幸被修改了返回地址,程序就会出现意外[1]

以上汇编代码由VC++6.0编译得到。

堆栈在EBP入栈后的情况:[1]

低位 高位

↓ ↓

内存地址 堆栈

┆ ┆

0012F600├────────┤← edi = 0012F600

│ │

0012F604├─┄┄┄ ┄─┤

│ │

│ │

┆ 44h的空间 ┆

┆ ┆

│ │

│ │

0012F640├─┄┄┄┄─┤

│ │

0012F644├────────┤← ebp被赋值后指向该单元,此时ebp=0012F644

│AC F6 12 00 │ebp赋值为esp之前的值

0012F648├────────┤

│C1 1F 40 00 │返回地址

0012F64C├────────┤← ebp + 8

│A0 F6 12 00 │函数实参p的值;

0012F650├────────┤

│ │

├────────┤

┆ ┆[1]

注:存储器存储空间堆栈按从高到低的排列,左边标注的地址是其右下方存储单元的最低位地址。如0012F644指向0012F6AC的AC字节,AC在栈顶。图中存储器中的内容按从低到高位书写,“AC F6 12 00”= 0x0012F6AC[1]

说明

(1)一个c程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们分别放在若干个源文件中,由若干个源程序文件组成一个c程序。这样便于分别编写和编译,调高调试效率。一个源程序文件可以为多个c程序公用。[1]

(2)一个源程序文件由一个或多个函数以及其他有关内容(如指令,数据声明与定义等)组成。一个源程序文件是一个编译单位,子啊程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。[1]

(3)c程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回main函数,在main函数中结束整个程序的进行。[1]

(4)所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数。main函数是被操作系统调用的。[1]

(5)从用户的角度来看函数分为两种

a:库函数,它是由系统提供的,用户不必自己定义,可直接使用它们。应该说明,不同的c语言编译系统提供的库函数的数量和功能会有一些不同,当然许多基本的函数是共同的。[1]

b:用户自己定义的函数。它是以解决用户专门需求的函数。[1]

(6)从函数的形式来看,函数分为两类。[1]

a:无参函数。无参函数可以带回或不带回函数值,但一般不带回函数值较多。[1]

b:有参函数。在调用函数时,主调函数在调用被调函数时,通过参数向被调函数传递数据。一般情况下,执行调用函数时会得到一个函数值,供主调函数使用。[1]

参考资料

1.

(美)Randal E.Bryant / David O'Hallaron.深入理解计算机系统:机械工业出版社,2010年

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数调用是编程中的一个基本操作,它允许程序在不同的代码块之间进行切换,并传递参数和执行不同的任务。函数调用底层实现原理涉及到许多复杂的计算机科学概念,包括内存管理、栈、寄存器、调用约定等。 在底层实现中,函数调用通常涉及以下几个步骤: 1. **参数传递**:当一个函数调用时,它的参数会被压入调用函数的栈帧中。这些参数包括输入和输出参数,以及局部变量。 2. **代码执行**:当函数开始执行时,控制权会转移到该函数的代码上。这个过程通常涉及到将程序的执行上下文(包括寄存器的内容、内存中的数据等)保存到栈帧中,以便函数执行完毕后可以恢复这些信息。 3. **返回地址保存**:当函数执行完毕并准备返回时,它会将程序计数器的当前值(即下一条要执行的指令的地址)保存到一个特殊的寄存器(通常是EIP)中,以便函数可以返回调用它的代码。 4. **返回**:函数执行完毕后,会从栈帧中取出返回地址(通常是EIP),然后跳转到这个地址继续执行程序。此时,函数调用就完成了。 这个过程在许多不同的编程语言中都是相似的,但是实现方式可能会有所不同。具体实现会取决于所使用的编程语言和操作系统,以及硬件架构(如x86、ARM等)。此外,不同的编译器和运行时环境可能会有不同的调用约定,这也会影响函数调用底层实现。 值得注意的是,函数调用底层实现通常涉及到许多底层的细节和复杂性,对于大多数编程任务来说并不需要了解这些细节。如果你对这方面的知识感兴趣,可以进一步学习计算机体系结构和操作系统课程,以了解更多关于函数调用和程序执行的基础知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值