[转载]visual studio的C/C++修饰名及调用约定(如__cdecl)

程序出链接错误的时候,经常看到lnk errorxxx:某某函数、某某变量找不到等等,里面的函数名通常都很难看明白,因为使用的是修饰名。

C 和 C++ 程序中的函数在内部通过其修饰名加以识别。修饰名是在编译函数定义或函数原型期间由编译器创建的字符串。

既然是编译器创建的字符串,不同的编译器使用的修饰名都不一样,这里讲的是visual studio的

C 函数的修饰形式取决于其声明中使用的调用约定。有三种调用约定,如下所示。

调用约定

修饰

__cdecl(默认值)

前导下划线 (_)

__stdcall

前导下划线 (_) 和结尾 at 符 (@),后面跟表示参数列表中的字节数的数字

__fastcall

__stdcall 相同,但前置符不是下划线,而是 @ 符

下面讲的修饰名、函数名变化规则和举得例子都是C语言的,C++的因为要考虑到重载所以更复杂,参见我的博文extern C

__cdecl是C/C++函数的默认调用约定。其堆栈由调用该函数的调用者(caller)来负责清理,因此该函数不需要知道自己有多少个参数,可以拥有可变参数(是vararg function)。也正因为如此,caller里面要有清理堆栈的代码(这里不是指程序员写的C/C++代码,而是编译器编译链接时要加上的二进制代码),所以以__cdecl方式调用子函数比用__stdcall方式调用,生成的可执行文件要更大一些。

参数传递顺序     从右到左

堆栈清理         调用者将参数弹出栈

修饰名           函数名前有下划线_,除非以C链接方式输出__cdecl函数

函数名           函数名没有变化

一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:

有一个类定义如下:

struct CMyClass {
   void __cdecl mymethod();
};

则类体外的定义

void CMyClass::mymethod() { return; }和void __cdecl CMyClass::mymethod() { return; }是等同的。

__stdcall,所有的windowsAPI都以__stdcall方式调用。因为windows.h里有定义:

#define WINAPI __stdcall

__stdcall方式调用函数的时候,由被调用函数(callee)清理堆栈,所以参数个数必须确定。

参数传递顺序     从右到左

堆栈清理         被调用者将参数弹出栈

参数传递         传值的拷贝,除非参数是指针或者引用

修饰名           函数名前有下划线_,函数名后面跟@,然后是所有参数的字节数(十进制)

函数名           函数名没有变化

因此函数int func( int a, double b )编译后的修饰名是_func@12  (int4字节,double8字节,共12字节)

一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:

有一个类定义如下:

struct CMyClass {
   void __stdcall mymethod();
};

则类体外的定义

void CMyClass::mymethod() { return; }和void __stdcall CMyClass::mymethod() { return; }是等同的。

__fastcall指的是如果可能,通过寄存器传递参数(所以叫做fast call)

参数传递顺序     前两个DWORD(unsigned long,32bit)或者更小尺寸的参数

                 (The first two DWORD or smaller arguments )通过ECX和EDX寄存器传递,

                 其余的从右到左

堆栈清理         调用者将参数弹出栈(所以参数个数也必须确定)

修饰名           函数名前有@,函数名后跟@,然后是参数的总字节数

函数名           函数名没有变化

因此函数int func( int a, double b )编译后的修饰名是 @func@12  (int4字节,double8字节,共12字节)

关于参数传递顺序,我的理解是:int func(DWORD i,DWORD j,int x,int y)当然是i、j分别用ECX和EDX传递,x、y从右到左通过栈传递。

int func(DWORDLONG i,DWORDLONG j,int x,int y),是x、y分别用ECX和EDX传递,64位的i和j从右到左通过栈传递

 

一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:

有一个类定义如下:

struct CMyClass {
   void __fastcall mymethod();
};

则类体外的定义

void CMyClass::mymethod() { return; }和void __fastcall CMyClass::mymethod() { return; }是等同的。

MSDN里给了调用约定的使用例子

// Example of the __cdecl keyword on function
int __cdecl system(const char *);
// Example of the __cdecl keyword on function pointer
typedef BOOL (__cdecl *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);

比较有意思的是 function pointer,我承认之前自己没有function pointer这个概念。特意到网上搜了下,转了篇博文Function Pointer(C)和Function Object(C++)

MSDN里还特别说明了,对于安腾处理器以及X64处理器,调用约定__cdecl和__stdcall被忽略。

对于安腾处理器以及AMD64(这里不是X64,所以不包括Intel-64)处理器,调用约定__fastcall被忽略。

在安腾处理器中,上述三种调用约定的参数全部通过寄存器传递。(不过对我们普通开发者没什么意义,与我何干)

visual studio里设置默认调用约定:项目属性-》C/C++ -》高级-》调用约定

建立新项目的时候默认调用约定是__cdecl。

原文地址:https://www.cnblogs.com/loongfee/p/2252149.html