Повторяющиеся деструкторы в выходных данных ассемблера для C

#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 для освобождения памяти.