VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

有编程疑问吗?还请到提问专区发帖提问!
搜索
查看: 427|回复: 0

[转载] C/C++ 函数的编译方式与调用约定

[复制链接]
51_avatar_middle
online_admins Syc 发表于 2018-1-16 17:02:49 | 显示全部楼层 |阅读模式
1、编译方式

C编译时函数名修饰约定规则:

__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式@functionname@number。

它们均不改变输出函数名中的字符大小写,这和pascal调用约定不同,pascal约定输出的函数名无任何修饰且全部大写。

c++编译时函数名修饰约定规则:

__stdcall调用约定:

1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面以“@@yg”标识参数表的开始,后跟参数表;
3、参数表以代号表示:

x--void ,
d--char,
e--unsigned char,
f--short,
h--int,
i--unsigned int,
j--long,
k--unsigned long,
m--float,
n--double,
_n--bool,
....
pa--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;

4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5、参数表后以“@z”标识整个名字的结束,如果该函数无参数,则以“z”标识结束。

其格式为“?functionname@@yg*****@z”或“?functionname@@yg*xz”,例如
int test1-----“?test1@@yghpadk@z”
void test2-----“?test2@@ygxxz”

__cdecl调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@yg”变为“@@ya”。

__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@yg”变为“@@yi”。

2、调用约定

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

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

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

调用约定用来:(一)处理决定函数参数传送时入栈和(二)出栈的顺序(由调用者还是被调用者把参数弹出栈),以及(三)编译器用来识别函数名称的名称修饰约定等问题。

1、__cdecl
__cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。

2、__stdcall
__stdcall调用约定用于调用Win32 API函数。采用__stdcal约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。

3、__fastcall
__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。

关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...->C/C++->Code Generation项选择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd,即__cdecl。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。

3、_stdcall与_cdecl调用约定对比

在“windef.h”头文件中可找到:
  1. #define CALLBACK __stdcall
  2. #define WINAPI __stdcall
  3. #define WINAPIV __cdecl
  4. #define APIENTRY WINAPI
  5. #define APIPRIVATE __stdcall
  6. #define PASCAL __stdcall
  7. #define cdecl _cdecl
  8. #ifndef CDECL#define CDECL _cdecl
  9. #endif
复制代码


几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,为什么?
首先,我们谈一下两者之间的区别:WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?如果我们的函数使用了__cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。


注意:

1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址。
2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:
#define CALLBACK __stdcall
#define WINAPI __stdcall

3、复杂函数声明或指针的修饰符示例:
extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);




上一篇:C++无符号int类型的位操作函数封装
下一篇:《程序设计基础》2017期末考试题,看大家能做对几道?

发帖求助前要善用论坛搜索功能,那里可能会有你要找的答案;

如果你已经在论坛发帖求助,并且从坛友或者管理的回复中解决了问题,请编辑帖子并把分类改成【已解决】

如何回报帮助你解决问题的坛友?可以给对方加【热心】【驿站币】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

关闭

站长提醒上一条 /1 下一条

QQ
QQ在线咨询
联系电话
13591366679
手机扫一扫 关注本站精彩内容
wxqrcode

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号tongdun|网站地图wx_jqr

GMT+8, 2018-10-21 21:33

Powered by Discuz! X3.4

© 2009-2018 cctry.com

快速回复 返回顶部 返回列表