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

时间:2012-02-07 08:26:52

标签: c++ c compiler-construction calling-convention

我发现了这个:

  

因为被调用的函数清理了堆栈,所以__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.对func1main()的调用,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.因为func2func2,所以我认为,编译器会自动插入代码(清理堆栈),如下所示:

__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更大?

4 个答案:

答案 0 :(得分:5)

  1. 它只会在调用后插入代码,即重置堆栈指针,只要有调用参数。*
  2. __stdcall在调用站点没有生成清理代码,但是,应该注意编译器可以将多个__cdecl调用的堆栈清理累积到一次清理中,或者它可以延迟清理以防止管道摊位。
  3. 忽略此示例中的反转顺序,不,它只会插入代码来清理__cdecl函数,设置函数参数是不同的(不同的编译器生成/偏好不同的方法)。
  4. __stdcall更像是一个窗户,请参阅this。二进制文件的大小取决于__cdecl funcs的调用次数,更多的调用意味着更多的清理代码,而__stdcall只有1个清理代码的单一实例。但是,你不应该看到那么多的大小增加,因为每次调用你只有几个字节。
  5. *区分清理和设置呼叫参数很重要。

答案 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中是这样 被激活了。属性的确切名称(__stdcallstdcall, 然而,可能会有所不同,因此您可能希望将其包装在宏中。

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

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