c++函数调用约定学习(二)

***********************************************************

首先,比较C++ 中的三种函数调用方式。

测试代码:

int _stdcall Add1(int x1, int x2, int x3)

{

return x1 + x2 + x3;

}

int __cdecl Add2(int x1, int x2, int x3)

{

return x1 + x2 + x3;

}

int __fastcall Add3(int x1, int x2, int x3)

{

return x1 + x2 + x3;

}

void FTest2()

{

int x[] = {1, 2, 3};

int ret;

ret = Add1(x[0], x[1], x[2]);

ret = Add2(x[0], x[1], x[2]);

ret = Add3(x[0], x[1], x[2]);

}

先看看在调用这些函数时调用者作些什么工作


__cdecl


_stdcall


__fastcall


ret = Add2(x[0], x[1], x[2]);


ret = Add1(x[0], x[1], x[2]);


ret = Add3(x[0], x[1], x[2]);


0040179F  mov         eax,dword ptr [ebp-4]

004017A2  push        eax

004017A3  mov         ecx,dword ptr [ebp-8]

004017A6  push        ecx

004017A7  mov         edx,dword ptr [x]

004017AA  push        edx

004017AB  call        Add2 (401750h)

004017B0  add         esp,0Ch

004017B3  mov         dword ptr [ret],eax


0040178B  mov         eax,dword ptr [ebp-4]

0040178E  push        eax

0040178F  mov         ecx,dword ptr [ebp-8]

00401792  push        ecx

00401793  mov         edx,dword ptr [x]

00401796  push        edx

00401797  call        Add1 (401760h)

0040179C  mov         dword ptr [ret],eax


004017B6  mov         eax,dword ptr [ebp-4]

004017B9  push        eax

004017BA  mov         edx,dword ptr [ebp-8]

004017BD  mov          ecx,dword ptr [x]

004017C0  call        Add3 (401730h)

004017C5  mov         dword ptr [ret],eax

从上面可以看出调用时区别:

__cdecl 调用结束后由调用者将参数出栈

_stdcall 调用结束后不需要调用者将参数出栈

__fastcall 调用结束后不需要调用者将参数出栈,并且调用前最左侧两个参数直接存入寄存器EDX 和ECX 中

相同之处是三者均由右向左压栈

再看看在这些函数自己作些什么工作


__cdecl


_stdcall


__fastcall


int __cdecl Add2(int x1, int x2, int x3)

{

00401750  push        ebp

00401751  mov         ebp,esp

return x1 + x2 + x3;

00401753  mov         eax,dword ptr [x1]

00401756  add         eax,dword ptr [x2]

00401759  add         eax,dword ptr [x3]

}

0040175C  pop         ebp

0040175D  ret


int _stdcall Add1(int x1, int x2, int x3)

{

00401760  push        ebp

00401761  mov         ebp,esp

return x1 + x2 + x3;

00401763  mov         eax,dword ptr [x1]

00401766  add         eax,dword ptr [x2]

00401769  add         eax,dword ptr [x3]

}

0040176C  pop         ebp

0040176D  ret         0Ch


int __fastcall Add3(int x1, int x2, int x3)

{

00401730  push        ebp

00401731  mov         ebp,esp

00401733  sub         esp,8

00401736  mov         dword ptr [ebp-8],edx

00401739  mov         dword ptr [ebp-4],ecx

return x1 + x2 + x3;

0040173C  mov         eax,dword ptr [x1]

0040173F  add         eax,dword ptr [x2]

00401742  add         eax,dword ptr [x3]

}

00401745  mov         esp,ebp

00401747  pop         ebp

00401748  ret         4

从上面可以看出函数自身的区别:

_stdcall 函数结束后在返回时ret 命令带个参数,代表参数所占的栈大小,在返回时会把参数弹出栈

__fastcall 函数结束后ret 命令同样带个参数,并且它在函数入口把寄存器中的参数再放入栈中,fast 这个词不名副其实啊!

----------------------------------------------------------------------------------------------

下面,以 __cdecl 调用为例,看看函数调用前后内存栈和主要寄存器的变化

测试代码:

int __cdecl Add2(int x1, int x2, int x3)

{

int tmp[10];

return x1 + x2 + x3;

}

ret = Add2(x[0], x[1], x[2]);

1. 在ret = Add2(x[0], x[1], x[2]); 执行前


内存地址


内存中存储的值


寄存器位置


0x74H



EBP


0x68H


X[3]


0x64H


ret


ESP




2. 当执行完下面红色语句后,进入Add2 之前

ret = Add2(x[0], x[1], x[2]);

004017AF  mov         eax,dword ptr [ebp-4]

004017B2  push         eax

004017B3  mov         ecx,dword ptr [ebp-8]

004017B6  push        ecx

004017B7  mov         edx,dword ptr [x]

004017BA  push        edx

004017BB  call        Add2 (401750h)

004017C0  add         esp,0Ch

004017C3  mov         dword ptr [ret],eax


内存地址


内存中存储的值


寄存器位置


0x74H



EBP


0x68H


X[3]


0x64H


ret


0x60H


X3


0x5BH


X2


0x58H


X1


0x54H


[EIP]


ESP

可见是参数不断压入栈中,最后压入要返回时的代码地址([EIP] 的值)

3. 进入函数Add2 正式执行用户代码前,执行红色代码后

int __cdecl Add2(int x1, int x2, int x3)

{

00401750  push        ebp

00401751  mov         ebp,esp

00401753  sub         esp,28h

int tmp[10];

return x1 + x2 + x3;

00401756  mov         eax,dword ptr [x1]

00401759  add         eax,dword ptr [x2]

0040175C  add         eax,dword ptr [x3]

}

0040175F  mov         esp,ebp

00401761  pop         ebp

00401762  ret


内存地址


内存中存储的值


寄存器位置


0x74H



0x68H


X[3]


0x64H


ret


0x60H


X3 (Add2())


0x5BH


X2


0x58H


X1


0x54H


[EIP]


0x50H


[EBP]


EBP


0x28H


Tmp[10]


ESP

这里主要工作是保存EBP, 并设置到栈顶,这样可以通过EBP 访问该函数中的局部变量。另外,由于函数内声明了局部变量 int tmp[10] ,占用大小 10 * 4 = 28H 个字节,所以把ESP 下移。(栈总是向下扩展)

4. 然后是执行函数体代码,返回值放入EAX( 或AX,AL) 中。最后返回,返回前重设ESP,EBP 的值,通过栈中的[EIP] 的值返回到调用函数之前的代码处。

最终:


内存地址


内存中存储的值


寄存器位置


0x74H



EBP


0x68H


X[3]


0x64H


ret


ESP


0x60H


X3 ( 没用了)


0x5BH


X2 ( 没用了)


0x58H


X1 ( 没用了)


0x54H


[EIP] ( 没用了)


0x50H


[EBP] ( 没用了)


0x28H


Tmp[10] ( 没用了)

时间: 2024-07-31 13:37:29

c++函数调用约定学习(二)的相关文章

c++函数调用约定学习(一)

函数调用约定 常见的函数调用约定[5]:cdecl,stdcall,fastcall,thiscall,naked call MFC调用约定(VS6:Project Settings->C/C++ <Category:Code Generation> Calling convention:) 1, __cdecl(C调用约定.The C default calling convention)C/C++ 缺省调用方式 1)压栈顺序:函数参数从右到左 2)参数栈维护:由调用函数把参数弹出栈,

C++语言学习(十二)——C++语言常见函数调用约定

C++语言学习(十二)--C++语言常见函数调用约定 一.C++语言函数调用约定简介 C /C++开发中,程序编译没有问题,但链接的时候报告函数不存在,或程序编译和链接都没有错误,但只要调用库中的函数就会出现堆栈异常等现象.上述现象出现在C和C++的代码混合使用的情况下或在C++程序中使用第三方库(非C++语言开发)的情况下,原因是函数调用约定(Calling Convention)和函数名修饰(Decorated Name)规则导致的.函数调用约定决定函数参数入栈的顺序,以及由调用者函数还是被

汇编 ? cdecl 函数调用约定,stdcall 函数调用约定

知识点: ? cdecl 函数调用约定 ? stdcall 函数调用约定 ? CALL堆栈平衡 配置属性--> c/c++ -->高级-->调用约定 一.cdecl调用约定 VC++默认约定__cdecl 1.源代码 int __cdecl add1(int a,int b) { return a+b; } 2.生成汇编代码 00401000 /$ 55 PUSH EBP 00401001 |. 8BEC MOV EBP,ESP 00401003 |. 8B45 08 MOV EAX,D

[Python 学习] 二、在Linux平台上使用Python

这一节,主要介绍在Linux平台上如何使用Python 1. Python安装. 现在大部分的发行版本都是自带Python的,所以可以不用安装.如果要安装的话,可以使用对应的系统安装指令. Fedora系统:先以root登入,运行 yum install python Ubuntu系统:在root组的用户, 运行 sudo apt-get install python 2. 使用的Python的脚本 Linux是一个以文件为单位的系统,那么我们使用的Python是哪一个文件呢? 这个可以通过指令

OpenCV for Python 学习 (二 事件与回调函数)

今天主要看了OpenCV中的事件以及回调函数,这么说可能不准确,主要是下面这两个函数(OpenCV中还有很多这些函数,可以在 http://docs.opencv.org/trunk/modules/highgui/doc/user_interface.html 找到,就不一一列举了),然后自己做了一个简单的绘图程序 函数如下: cv2.setMouseCallback(windowName, onMouse[, param]) cv2.createTrackbar(trackbarName,

Makefile持续学习二

Makefile概述 一.Makefile里有什么? Makefile里主要包含5个东西:显式规则.隐晦规则.变量定义.文件指示和注释 1.显式规则:显式规则说明如恶化生成一个或多的目标文件,包含要生成的文件,文件的依赖文件,生成的命令 2.隐晦规则:由make自动推动功能完成 3.变量定义:变量一般都是字符串,类似C语言中的宏定义,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上 4.文件指示: 在一个Makefile中引用另一个Makefile 根据某些情指定Makefil

redis ruby客户端学习( 二)

接上一篇redis ruby客户端学习( 二) 对于redis的五种数据类型:字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets),上一篇介绍了字符串. 1,哈希(Map) hset.设置 key 指定的哈希集中指定字段的值.如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联.如果字段在哈希集中存在,它将被重写. require "redis" r = Redis.new r.hset 'my_h

Duilib学习二 第一个程序 Hello World

Duilib学习二  第一个程序 Hello World #pragma once #include <UIlib.h> using namespace DuiLib; #ifdef _DEBUG # ifdef _UNICODE # pragma comment(lib, "DuiLib_ud.lib") # else # pragma comment(lib, "DuiLib_d.lib") # endif #else # ifdef _UNICOD

C/C++函数调用约定

C/C++函数调用约定 函数声明部分的extern "C"表示连接规范(Linkage Specification)采用C,而不是C++.如果不写的 话.默认采用C++,当然也可以写成extern "C++". 1.__cdecl: C和C++默认的函数调用约定,参数从右到左顺序压入堆栈,由函数负责清理堆栈,把参数弹出. 也正是因为用来传送参数的堆栈是由调用函数维护的,所以实现可变参数的函数只能使用这种函数调用约定.因为每一个调用它的函数都要包含清理堆栈的代码,所以