C ++構造体の奇妙な振る舞いに気づいた後、同僚がこの質問で私を突いた。
この些細なコードを見てください:
struct S {
int i;
#ifdef TEST
~S() {}
#endif
};
void foo (S s) {
(void)s;
}
int main () {
foo(S());
return 0;
}
明示的なデストラクタなしでアセンブリコードを1回生成しました。
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
main
これはinのコード[1]ですdestructor_no.s
:
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で引数を渡すために使用される最初のレジスタです。2番目のケースでは、代わりに、構造体がスタックに割り当てられますが、
foo()
関数は。のポインターを使用して呼び出されます%rdi
。
なぜそのような違いがあるのですか?
ノート:
gcc-4.6.3
、、またはを使用しても同じ動作が確認されclang 3.1
ます。もちろん、最適化が有効になっている場合、関数の呼び出し
foo()
はどのような場合でも完全に最適化されます。struct
デストラクタが明示的に提供されていない場合、に変数を追加すると、興味深いパターンが現れます。
最大4int
秒(= 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
しかし、構造体に5番目を追加するとすぐに、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]この質問の目的のために不要だと思う行をいくつか削除しました。