Коллега задал мне этот вопрос, заметив любопытное поведение со структурами 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
Это код [1] для main
в 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.
во втором случае вместо структуры выделяется в стеке, но 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 ++.
Других решений пока нет …