笔记:C++中常见函数修饰关键字:extern “C“、_cdecl、__stdcall、__fastcall、__declspec(dllexport)和__declspec(dllimport)

一、函数修饰关键字的作用
  1. 确定函数名,便于链接时链接到正确函数地址。
  2. 确定函数调用栈的汇编代码。
二、详细说明

1. 先声明未定义和实现函数

//声明未实现的C++函数
void CppDefault(const char* pMsg);
void __cdecl CppCdecl(const char* pMsg);
void __stdcall CppStdcall(const char* pMsg);
void __fastcall CppFastcall(const char* pMsg);
void __declspec(dllexport) CppDllexport(const char* pMsg);
void __declspec(dllimport) CppDllimport(const char* pMsg);

//声明带 extern "C" 的未实现函数
extern "C" {
	void CDefault(const char* pMsg);
	void __cdecl CCdecl(const char* pMsg);
	void __stdcall CStdcall(const char* pMsg);
	void __fastcall CFastcall(const char* pMsg);
	void __declspec(dllexport) CDllexport(const char* pMsg);
	void __declspec(dllimport) CDllimport(const char* pMsg);
}

2. 直接运行查看VS报错,从报错中确认生成的函数名
在这里插入图片描述
3. 函数名:确认不同关键字对其影响

  • 所有不带 extern “C” 关键字均以C++的规范生成函数名,以 ? 开头,且函数名通过@带有参数和其他信息,这是C++支持重载的本质原因。
  • extern “C” 关键字时,__cdecl修饰的函数以下划线开头,__stdcall修饰的函数以下划线开头 且 结尾用@带上参数的大小,__fastcall修饰的函数以@开头 且 结尾用@带上参数的大小。
  • C++中默认调用约定为:__cdecl,即:缺省调用约定为__cdecl。
  • __declspec(dllexport) 只影响DLL函数的导出名,不影响DLL内部调用的名字。从 __declspec(dllimport) 可以看出,DLL导出函数时会在函数名前加上 __imp__imp 后面的下划线数量受程序位数影响,如:Win32为两个,X64为一个。但不带extern "C"关键字时,固定为一个,因为可通过C++函数命名规则中的@直接带上程序的位数信息。

3. 函数栈:确认不同关键字对其影响

void __cdecl TestCdecl(int a, int b, int c, int d)
{
	std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

void __stdcall TestStdcall(int a, int b, int c, int d)
{
	std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

void __fastcall TestFastcall(int a, int b, int c, int d)
{
	std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

上面定义了三种不同调用约定的函数,进行函数调用,查看其被调用处的汇编代码,如下:

    57: 	TestCdecl(1, 2, 3, 4);
00EC638E 6A 04                push        4  
00EC6390 6A 03                push        3  
00EC6392 6A 02                push        2  
00EC6394 6A 01                push        1  
00EC6396 E8 D0 AF FF FF       call        TestCdecl (0EC136Bh)  
00EC639B 83 C4 10             add         esp,10h  
    58: 	TestStdcall(1, 2, 3, 4);
00EC639E 6A 04                push        4  
00EC63A0 6A 03                push        3  
00EC63A2 6A 02                push        2  
00EC63A4 6A 01                push        1  
00EC63A6 E8 7D AC FF FF       call        TestStdcall (0EC1028h)  
    59: 	TestFastcall(1, 2, 3, 4);
00EC63AB 6A 04                push        4  
00EC63AD 6A 03                push        3  
00EC63AF BA 02 00 00 00       mov         edx,2  
00EC63B4 B9 01 00 00 00       mov         ecx,1  
00EC63B9 E8 64 AD FF FF       call        TestFastcall (0EC1122h)  

从上面的汇编代码,对__cdecl、__stdcall和__fastcall进行对比,可以看出:

  • 三种调用方式的参数压栈顺序都是从右往左。
  • __cdecl对比另外两种,其在调用完函数后,会修改栈顶寄存器esp,本质是释放参数压栈所占用的栈空间,另外两个在调用完成后没有释放,是因为其函数内部在返回前,已经通过执行ret n指令(n为形参的占用空间)完成了释放,无需重复释放。
  • __cdecl由 调用者 释放形参的栈空间,其好处是可以支持函数的变长参数,因为不同调用地方的释放代码可不一样,进而可完成不同形参空间的释放,但其缺点是每个调用的地方都会多一行释放栈空间的代码,会增加程序大小。另外两个由于释放形参栈空间的代码在其函数内部,无法完成不同数量形参的不同大小的释放,故不支持变长参数的函数,但好处是程序的大小会相对较小。
  • __fastcall之所以快速,是因为其利用ecx和edx两个寄存器来存储前两个形参,省去了前两个参数的压栈操作,故被称为快速调用。注意:利用寄存器传形参的思想已经被大量应用于64位程序。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值