为C++结构定义显式析构函数如何影响调用约定
How does defining an explicit destructor for a C++ struct affect calling conventions?
一位同事在注意到C++结构的一个奇怪行为后,向我提出了这个问题。
以这个琐碎的代码为例:
struct S {
int i;
#ifdef TEST
~S() {}
#endif
};
void foo (S s) {
(void)s;
}
int main () {
foo(S());
return 0;
}
我已经生成了一次没有显式析构函数的汇编代码:
g++-4.7.2 destructor.cc -S -O0 -o destructor_no.s
后来包括它:
g++-4.7.2 destructor.cc -DTEST -S -O0 -o destructor_yes.s
这是destructor_no.s
:中main
的代码[1]
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
movl %eax, %edi
call _Z3foo1S // call to foo()
movl $0, %eax
popq %rbp
ret
相反,如果析构函数是明确定义的:
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, -16(%rbp)
leaq -16(%rbp), %rax
movq %rax, %rdi
call _Z3foo1S // call to foo()
leaq -16(%rbp), %rax
movq %rax, %rdi
call _ZN1SD1Ev // call to S::~S()
movl $0, %eax
leave
ret
现在,我的组装知识有点生疏,但在我看来:
在第一种情况下,结构是"按值"传递的。也就是说,它的内存内容被复制到
%edi
寄存器中,如果我没有记错的话,它是x86-64
ABI中用于传递参数的第一个寄存器。在第二种情况下,结构是在堆栈上分配的,但
foo()
函数是用%rdi
中的指针调用的。
为什么会有这样的差异
注意:
如果使用
gcc-4.6.3
或clang 3.1
,则会确认相同的行为。当然,如果启用了优化,那么对函数
foo()
的调用在任何情况下都会被完全优化掉。如果没有显式提供析构函数,则在向
struct
添加更多变量时会出现一个有趣的模式。
通过参数寄存器最多可传递4个int
s(=16字节):
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, -16(%rbp)
movl $0, -12(%rbp)
movl $0, -8(%rbp)
movl $0, -4(%rbp)
movq -16(%rbp), %rdx
movq -8(%rbp), %rax
movq %rdx, %rdi
movq %rax, %rsi
call _Z3foo1S
但是,当我向结构中添加第五个int
时,仍然"按值"传递的函数的参数现在就在堆栈上了:
pushq %rbp
movq %rsp, %rbp
subq $56, %rsp
movl $0, -32(%rbp)
movl $0, -28(%rbp)
movl $0, -24(%rbp)
movl $0, -20(%rbp)
movl $0, -16(%rbp)
movq -32(%rbp), %rax
movq %rax, (%rsp)
movq -24(%rbp), %rax
movq %rax, 8(%rsp)
movl -16(%rbp), %eax
movl %eax, 16(%rsp)
call _Z3foo1S
[1] 我删除了一些我认为不必要的行。
在C++03中,如果定义了析构函数,则结构不再是POD类型。变量中没有析构函数的对象的行为类似于C结构变量(因此它只是按值传递),而具有用户定义的对象的操作类似于C++对象。
相关文章:
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- "std::unique_XXX"命名约定背后的基本原理是什么?
- 为什么擦除方法会影响结束方法
- 内联如何影响模块接口中的成员函数
- 为什么在 x64 中忽略__stdcall调用约定?
- 为什么返回类型的'const'限定符对标有 __forceinline/内联的函数没有影响?
- 在容量内调整矢量大小时的性能影响
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 未达到的情况会影响开关外壳性能
- 引发异常的方法的命名约定 (C++)?
- 循环仅对第一行正常工作.其他行不受 for 的影响
- 处理影响跨不同线程共享对象的定时回调的最佳方法是什么?
- 模板如何影响C++中隐式声明的规则?
- 窗口调用约定
- 命名空间信息会影响C++的可读性
- 我应该提到纯虚函数中的调用约定吗?
- [[可能]]和[[不太可能]]影响程序汇编的简单示例?
- 如何保护非托管应用程序中的字符串不受进程转储的影响
- 检查nullptr是否100%保护内存布局不受segfault影响
- 为C++结构定义显式析构函数如何影响调用约定