我发现了这个:
因为被调用的函数清理了堆栈,所以__stdcall 调用约定创建较小的可执行文件而不是__cdecl,其中 必须为每个函数调用生成堆栈清理代码。
假设我有两个功能:
void __cdecl func1(int x)
{
//do some stuff using x
}
void __stdcall func2(int x, int y)
{
//do some stuff using x, y
}
以及main()
:
int main()
{
func1(5);
func2(5, 6);
}
IMO,main()
负责清理对func1(5)
的通话堆栈,func2
将清除对func2(5,6)
的通话堆栈,对吗?
四个问题:
1.对func1
中main()
的调用,main
负责清理堆栈,编译器会插入一些代码(清理堆栈的代码)在致电func
之前和之后?像这样:
int main()
{
before_call_to_cdecl_func(); //compiler generated code for stack-clean-up of cdecl-func-call
func1(5);
after_call_to_cdecl_func(); //compiler generated code for stack-clean-up of cdecl-func-call
func2(5, 6);
}
2.对于func2
中对main()
的调用,它是func2
自己的工作来清理堆栈,所以我认为,{{1}中不会插入任何代码调用main()
之前或之后,对吧?
3.因为func2
是func2
,所以我认为,编译器会自动插入代码(清理堆栈),如下所示:
__stdcall
我认为对吗?
4.最后,回到引用的字词,为什么void __stdcall func1(int x, int y)
{
before_call_to_stdcall_func(); //compiler generated code for stack-clean-up of stdcall-func-call
//do some stuff using x, y
after_call_to_cdecl_func(); //compiler generated code for stack-clean-up of stdcall-func-call
}
导致可执行文件比__stdcall
更小?在linux中没有__cdecl
这样的东西,对吧?这是否意味着linux elf在win中总是比exe更大?
答案 0 :(得分:5)
__stdcall
在调用站点没有生成清理代码,但是,应该注意编译器可以将多个__cdecl
调用的堆栈清理累积到一次清理中,或者它可以延迟清理以防止管道摊位。__cdecl
函数,设置函数参数是不同的(不同的编译器生成/偏好不同的方法)。__stdcall
更像是一个窗户,请参阅this。二进制文件的大小取决于__cdecl
funcs的调用次数,更多的调用意味着更多的清理代码,而__stdcall
只有1个清理代码的单一实例。但是,你不应该看到那么多的大小增加,因为每次调用你只有几个字节。*区分清理和设置呼叫参数很重要。
答案 1 :(得分:3)
从历史上看,第一批C ++编译器使用的相当于
__stdcall
。从实施的质量来看,我期待
C编译器使用__cdecl
对象和C ++编译器
__stdcall
(当时称为Pascal召唤)。
这是早期Zortech编译的一件事。
当然,vararg函数仍然必须使用__cdecl
约定。该
如果不知道要清理多少,callee就无法清理堆栈。
(注意,C标准经过精心设计,允许使用
C中的__stdcall
约定。我只知道一个编译器
然而,利用这一点;当时现有代码的数量
在没有原型的情况下调用vararg函数是巨大的,
当标准声明它被破坏时,编译器实现者没有
想破坏客户的代码。)
在许多环境中,似乎有一种强烈的坚持倾向
C和C ++约定是相同的,可以采用
extern "C++"
函数的地址,并将其传递给写入的函数
在C中调用它。例如,IIRC,g ++不会处理
extern "C" void f();
和
void f();
有两种不同的类型(虽然标准要求),和
允许传递静态成员函数的地址
例如pthread_create
。结果是这样的编译器使用
各地都有完全相同的惯例,而在英特尔,他们就是
相当于__cdecl
。
许多编译器都有扩展来支持其他对话。 (为什么他们
不要使用标准extern "xxx"
,我不知道。)语法
然而,这些扩展是多种多样的。微软提出了这个属性
直接在函数名称之前:
void __stdcall func( int, int );
,g ++将其放在函数后面的特殊属性子句中 声明:
void func( int, int ) __attribute__((stdcall));
C ++ 11添加了一种指定属性的标准方法:
void [[stdcall]] func( int, int );
它没有将stdcall
指定为属性,但 指定了该属性
其他属性(标准中定义的属性除外)可能是
指定,并依赖于实现。我希望g ++和
VC ++在最新版本中接受这种语法,至少在C ++ 11中是这样
被激活了。属性的确切名称(__stdcall
,stdcall
,
然而,可能会有所不同,因此您可能希望将其包装在宏中。
最后:在打开优化的现代编译器中,
调用约定的差异可能是微不足道的。
属性如const
(不要与C ++关键字混淆
const
),regparm
或noreturn
可能会产生更大的影响,
无论是在可执行文件大小和性能方面。
答案 2 :(得分:1)
此呼叫约会人群是新的 64位ABI 的历史记录。
http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
对于不同的架构,还有ABI方面的东西。 (比如 ARM ) 并非所有架构都执行相同的操作。所以不要费心考虑这个召唤惯例!
http://en.wikipedia.org/wiki/Calling_convention
EXE尺寸的改善是微不足道的(可能不存在),不要打扰......
__cdecl
比__stdcall
灵活得多。可变数量的参数灵活性,清理代码(指令)的无意义,__cdecl
函数可以使用错误数量的参数调用,这不一定会导致严重的问题!但__stdcall
的情况总是出错!
答案 3 :(得分:0)
其他人已经回答了你问题的其他部分,所以我只想添加关于大小的答案:
4.最后,回到引用的单词,为什么__stdcall导致比__cdecl更小的可执行文件?
这似乎不对。我通过使用和不使用stdcall调用约定来编译libudis来测试它。首先没有:
$ clang -target i386-pc-win32 -DHAVE_CONFIG_H -Os -I.. -I/usr/include -fPIC -c *.c && strip *.o
$ du -cb *.o
6524 decode.o
95932 itab.o
1434 syn-att.o
1706 syn-intel.o
2288 syn.o
1245 udis86.o
109129 totalt
随着。是-mrtd
开关启用stdcall:
$ clang -target i386-pc-win32 -DHAVE_CONFIG_H -Os -I.. -I/usr/include -fPIC -mrtd -c *.c && strip *.o
7084 decode.o
95932 itab.o
1502 syn-att.o
1778 syn-intel.o
2296 syn.o
1305 udis86.o
109897 totalt
正如你所看到的,cdecl用几百个字节击败了stdcall。它可能是我的测试方法有缺陷,或者clang的stdcall代码生成器很弱。但我认为,对于现代编译器,调用者清理所提供的额外灵活性意味着他们将始终使用cdecl而不是stdcall生成更好的代码。