__cdecl导致比__stdcall更大的可执行文件?

我找到了这个:

由于堆栈由被调用函数清理,因此__stdcall调用约定创建比__cdecl 更小的可执行文件,其中必须为每个函数调用生成堆栈清理代码

假设我有两个function:

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.为了调用main() func1main()负责清理堆栈,编译器在调用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.对于main() func2的调用,它是func2自己的工作来清理堆栈,所以我认为,在调用func2之前或之后, main()不会插入任何代码,对吧?

3.因为func2__stdcall ,所以我认为,编译器会自动插入代码(清理堆栈),如下所示:

 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 } 

我猜对了吗?

4.最后,回到引用的单词,为什么__stdcall导致比__cdecl更小的可执行文件? 在linux中没有__stdcall这样的东西,对吧? 这是否意味着linux elf在win中总是比exe更大?

  1. 它只会在调用后插入代码,即重置堆栈指针,只要有调用参数。*
  2. __stdcall在调用站点没有生成清理代码,但是,应该注意编译器可以将多个__cdecl调用的堆栈清理累积到一次清理中,或者它可以延迟清理以防止管道停顿。
  3. 忽略这个例子中的反转顺序,不,它只会插入代码来清理__cdecl函数,设置函数参数是不同的(不同的编译器生成/偏好不同的方法)。
  4. __stdcall更像是一个windows的东西,看到这个 。 二进制文件的大小取决于对__cdecl函数的调用次数,更多的调用意味着更多的清理代码,其中__stdcall只有1个清理代码的单一实例。 但是,您不应该看到大小增加,因为每次调用最多只有几个字节。

*区分清理和设置呼叫参数很重要。

从历史上看,第一批C ++编译器使用的是__stdcall 。 从实现的质量来看,我希望C编译器使用__cdecl ,而C ++编译器使用__stdcall (当时称为Pascal对等)。 这是早期Zortech编译得对的一件事。

当然,vararg函数仍然必须使用__cdecl约定。 如果不知道要清理多少,则被调用者无法清理堆栈。

(注意,C标准经过精心设计,允许在C中使用__stdcall约定。但我只知道一个利用了这个的编译器;当时调用vararg函数的现有代码量没有原型在视图中是巨大的,虽然标准声明它被破坏,编译器实现者不想打破他们客户的代码。)

在许多环境中,似乎存在一种非常强烈的倾向,即坚持C和C ++约定相同,可以使用extern "C++"函数的地址,并将其传递给用C编写的函数。它叫它。 例如,IIRC,g ++不会对待

 extern "C" void f(); 

 void f(); 

因为有两种不同的类型(虽然标准需要它),并允许将静态成员函数的地址传递给pthread_create 。 结果是这样的编译器在所有地方使用完全相同的约定,而在英特尔,它们相当于__cdecl

许多编译器都有扩展来支持其他对话。 (为什么他们不使用标准的extern "xxx" ,我不知道。)然而,这些扩展的语法是多种多样的。 Microsoft将该属性直接放在函数名称之前:

 void __stdcall func( int, int ); 

,g ++在函数声明后将它放在一个特殊的属性子句中:

 void func( int, int ) __attribute__((stdcall)); 

C ++ 11添加了一种指定属性的标准方法:

 void [[stdcall]] func( int, int ); 

它没有将stdcall指定为属性,但它确实指定可以指定其他属性(标准中定义的属性除外),并且是依赖于实现的。 我希望g ++和VC ++都能在最新版本中接受这种语法,至少在C ++ 11被激活时是这样。 但是,属性的​​确切名称( __stdcallstdcall等)可能会有所不同,因此您可能希望将其包装在宏中。

最后:在打开优化的现代编译器中,调用约定的差异可能是微不足道的。 像const这样的属性(不要与C ++关键字const混淆), regparmnoreturn可能会在可执行文件大小和性能方面产生更大的影响。

这个召集会议的人群是新的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情况总是出错!

其他人已经回答了你问题的其他部分,所以我只想添加关于大小的答案:

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生成更好的代码。