龙空技术网

C语言编程之函数调用规则

嵌入式编程爱好者 937

前言:

而今咱们对“c语言中函数调用的方式”大体比较注意,小伙伴们都需要了解一些“c语言中函数调用的方式”的相关知识。那么小编也在网摘上收集了一些有关“c语言中函数调用的方式””的相关文章,希望小伙伴们能喜欢,兄弟们快快来学习一下吧!

函数的调用规则多数情况下是不需要了解的。但是在跨语言和跨平台编程,比如DLL和COM编程时,就需要了解是怎么回事,因为这涉及到函数参数进出栈的顺序和清理工作。

例如:microsoft的vc默认的是__cdecl方式,而windows API则是__stdcall,如果用vc开发dll给其他语言用,则应该指定__stdcall方式。堆栈由谁清除这个很重要,如果是要写汇编函数给C调用,一定要小心堆栈的清除工作,如果是__cdecl方式的函数,则函数本身(如果不用汇编写)则不需要关心保存参数的堆栈的清除,但是如果是__stdcall的规则,一定要在函数退出(ret)前恢复堆栈。

调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。

在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器。这项技术通常被称为名称改编(Name Mangling)或者名称修饰(Name Decoration)。许多C++编译器厂商选择了自己的名称修饰方案。

因此,为了使其它语言编写的模块(如Visual Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰。

函数调用规则主要有以下几种:

1、__cdecl

__cdecl调用规则就是C调用规则。按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。返回值在EAX中因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。

int f(int a,int b);//不加修饰,默认__cdecl调用

int __cdecl f(int a,int b);//明确指出__cdecl调用。

示例程序:

1: #include <stdio.h>

2:

3: int __cdecl f(int a,int b)

4: {

5: int c;

6: c = a + b;

7: return c;

8: }

汇编代码如下:注意最后一句ret,后面没有参数。

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,44h

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 lea edi,[ebp-44h]

0040102C mov ecx,11h

00401031 mov eax,0CCCCCCCCh

00401036 rep stos dword ptr [edi]

00401038 mov eax,dword ptr [ebp+8]

0040103B add eax,dword ptr [ebp+0Ch]

0040103E mov dword ptr [ebp-4],eax

00401041 mov eax,dword ptr [ebp-4]

00401044 pop edi

00401045 pop esi

00401046 pop ebx

00401047 mov esp,ebp

00401049 pop ebp

0040104A ret

***可变参数函数只能是__cdecl调用规则!***

2、__stdcall

__stdcall也是按从右至左的顺序压参数入栈,但是它是由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。  __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12。

示例程序:

1: #include <stdio.h>

2:

3: int __stdcall f(int a,int b)

4: {

5: int c;

6: c = a + b;

7: return c;

8: }

汇编代码如下:注意最后一句ret 8!与__cdecl相比多了个参数8!

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,44h

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 lea edi,[ebp-44h]

0040102C mov ecx,11h

00401031 mov eax,0CCCCCCCCh

00401036 rep stos dword ptr [edi]

00401038 mov eax,dword ptr [ebp+8]

0040103B add eax,dword ptr [ebp+0Ch]

0040103E mov dword ptr [ebp-4],eax

00401041 mov eax,dword ptr [ebp-4]

00401044 pop edi

00401045 pop esi

00401046 pop ebx

00401047 mov esp,ebp

00401049 pop ebp

0040104A ret 8

3、__fastcall

__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。这个和__stdcall很象,唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的,即第一个参数进ECX,第2个进EDX,其他参数是从右向左的入stack。返回仍然通过EAX..

示例程序:

1: #include <stdio.h>

2:

3: int __stdcall f(int a,int b)

4: {

5: int c;

6: c = a + b;

7: return c;

8: }

汇编代码如下,注意比较其与__stdcall和__cdecl在寄存器上的区别。

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,4Ch

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 push ecx

0040102A lea edi,[ebp-4Ch]

0040102D mov ecx,13h

00401032 mov eax,0CCCCCCCCh

00401037 rep stos dword ptr [edi]

00401039 pop ecx

0040103A mov dword ptr [ebp-8],edx

0040103D mov dword ptr [ebp-4],ecx

00401040 mov eax,dword ptr [ebp-4]

00401043 add eax,dword ptr [ebp-8]

00401046 mov dword ptr [ebp-0Ch],eax

00401049 mov eax,dword ptr [ebp-0Ch]

0040104C pop edi

0040104D pop esi

0040104E pop ebx

0040104F mov esp,ebp

00401051 pop

~~~end~~~

标签: #c语言中函数调用的方式