__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()
func1
, 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.对于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更大?
- 它只会在调用后插入代码,即重置堆栈指针,只要有调用参数。*
-
__stdcall
在调用站点没有生成清理代码,但是,应该注意编译器可以将多个__cdecl
调用的堆栈清理累积到一次清理中,或者它可以延迟清理以防止管道停顿。 - 忽略这个例子中的反转顺序,不,它只会插入代码来清理
__cdecl
函数,设置函数参数是不同的(不同的编译器生成/偏好不同的方法)。 -
__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被激活时是这样。 但是,属性的确切名称( __stdcall
, stdcall
等)可能会有所不同,因此您可能希望将其包装在宏中。
最后:在打开优化的现代编译器中,调用约定的差异可能是微不足道的。 像const
这样的属性(不要与C ++关键字const
混淆), regparm
或noreturn
可能会在可执行文件大小和性能方面产生更大的影响。
这个召集会议的人群是新的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生成更好的代码。