#c #gcc #assembly #arm #code-generation
#c #gcc #сборка #arm #генерация кода
Вопрос:
В настоящее время я пытаюсь понять, как G генерирует сборку из небольшого примера программы на C . Итак, я тестирую следующую программу с различными оптимизациями:
#define noinline __attribute__ ((noinline))
class Test_Base {
public:
noinline Test_Base() {}
virtual noinline ~Test_Base() {}
void noinline method() { v_method(); }
private:
virtual void v_method()=0;
};
class Test1
: public Test_Base
{
public:
noinline Test1() {}
noinline ~Test1() {}
private:
void noinline v_method() {}
};
class Test2
: public Test_Base
{
public:
noinline Test2() {}
~Test2() {}
private:
void noinline v_method() {}
};
int main() {
volatile int x = 0;
Test_Base * b;
Test1 t1;
Test2 t2;
if( x )
b = amp;t1;
else
b = amp;t2;
b->method();
return 0;
}
Однако, просмотрев этот код (скомпилированный с использованием -Os
платформы ARMv7), я обнаружил, что все определения конструкторов и деструкторов были включены несколько раз. Вот соответствующие части таблицы символов для Test1:
00008730 w F .text 00000004 Test1::v_method()
000088d8 w O .rodata 00000014 vtable for Test1
000087d0 w F .text 00000020 Test1::Test1()
00008774 w F .text 0000001c Test1::~Test1()
00008710 w F .text 00000020 Test1::~Test1()
000088a4 w O .rodata 00000007 typeinfo name for Test1
00008898 w O .rodata 0000000c typeinfo for Test1
000087d0 w F .text 00000020 Test1::Test1()
00008710 w F .text 00000020 Test1::~Test1()
Итак, у меня есть один конструктор и два деструктора (последние два вызова — это просто дубликаты с теми же позициями, что и раньше). Глядя на сборку, я наблюдаю следующее:
Сначала конструктор
000087d0 <Test1::Test1()>:
87d0: e92d4010 push {r4, lr}
87d4: e1a04000 mov r4, r0
87d8: ebfffff3 bl 87ac <Test_Base::Test_Base()>
87dc: e1a00004 mov r0, r4
87e0: e59f3004 ldr r3, [pc, #4] ; 87ec <Test1::Test1() 0x1c>
87e4: e5843000 str r3, [r4]
87e8: e8bd8010 pop {r4, pc}
87ec: 000088e0 .word 0x000088e0
Я думаю, это делает то, что я сказал ему делать.
Теперь деструктор в 0x8710:
00008710 <Test1::~Test1()>:
8710: e59f3014 ldr r3, [pc, #20] ; 872c <Test1::~Test1() 0x1c>
8714: e92d4010 push {r4, lr}
8718: e1a04000 mov r4, r0
871c: e5803000 str r3, [r0]
8720: ebfffff6 bl 8700 <Test_Base::~Test_Base()>
8724: e1a00004 mov r0, r4
8728: e8bd8010 pop {r4, pc}
872c: 000088e0 .word 0x000088e0
Опять же, здесь нет ничего подозрительного.
Теперь деструктор в 0x8774:
00008774 <Test1::~Test1()>:
8774: e92d4010 push {r4, lr}
8778: e1a04000 mov r4, r0
877c: ebffffe3 bl 8710 <Test1::~Test1()>
8780: e1a00004 mov r0, r4
8784: ebffff69 bl 8530 <_init 0x44>
8788: e1a00004 mov r0, r4
878c: e8bd8010 pop {r4, pc}
Я не могу точно сказать, что это делает, поскольку я не очень знаком с ABI. Я предполагаю, что это как-то связано со статической инициализацией.
Какова цель этого дополнительного деструктора?
Если я скомпилирую то же самое для x86_64, я также получу дублированные деструкторы, так что это не кажется специфичным для системы.
Ответ №1:
Первый — это невиртуальный деструктор, используемый для уничтожения автоматических или статических объектов, когда динамический тип известен во время компиляции.
Второй — это виртуальный «thunk», используемый для полиморфного удаления. Он вызывает невиртуальный деструктор для уничтожения объекта, а затем вызывает operator delete
для освобождения памяти.