"чистая виртуальная функция, называемая" на gcc 4.4 ", но не на более новой версии или clang 3.4

У меня есть MCVE, который на некоторых моих машинах вылетает при компиляции с g++ версии 4.4.7, но работает с clang++ версии 3.4.2 и g++ версии 6.3.

Мне нужна помощь, чтобы узнать, исходит ли это из поведения undefined или от фактической ошибки этой древней версии gcc.

Код

#include <cstdlib>

class BaseType
{
public:
    BaseType() : _present( false ) {}
    virtual ~BaseType() {}

    virtual void clear() {}

    virtual void setString(const char* value, const char* fieldName)
    {
        _present = (*value != '\0');
    }

protected:
    virtual void setStrNoCheck(const char* value) = 0;

protected:
    bool _present;
};

// ----------------------------------------------------------------------------------

class TypeTextFix : public BaseType
{
public:
    virtual void clear() {}

    virtual void setString(const char* value, const char* fieldName)
    {
        clear();
        BaseType::setString(value, fieldName);
        if( _present == false ) {
            return; // commenting this return fix the crash. Yes it does!
        }
        setStrNoCheck(value);
    }

protected:
    virtual void setStrNoCheck(const char* value) {}
};

// ----------------------------------------------------------------------------------

struct Wrapper
{
    TypeTextFix _text;
};

int main()
{
    {
        Wrapper wrapped;
        wrapped._text.setString("123456789012", NULL);
    }
    // if I add a write to stdout here, it does not crash oO
    {
        Wrapper wrapped;
        wrapped._text.setString("123456789012", NULL); // without this line (or any one), the program runs just fine!
    }
}

Скомпилировать и запустить

g++ -O1 -Wall -Werror thebug.cpp && ./a.out
pure virtual method called
terminate called without an active exception
Aborted (core dumped)

Это фактически минимально, если удалять любую функцию этого кода, он работает правильно.

Проанализировать

Фрагмент кода отлично работает при компиляции с помощью -O0, но он все еще отлично работает при компиляции с -O0 +flag для каждого флага -O1, как определено на Документация GnuCC.

Создается дамп ядра, из которого можно извлечь обратную трассировку:

(gdb) bt
#0  0x0000003f93e32625 in raise () from /lib64/libc.so.6
#1  0x0000003f93e33e05 in abort () from /lib64/libc.so.6
#2  0x0000003f98ebea7d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib64/libstdc++.so.6
#3  0x0000003f98ebcbd6 in ?? () from /usr/lib64/libstdc++.so.6
#4  0x0000003f98ebcc03 in std::terminate() () from /usr/lib64/libstdc++.so.6
#5  0x0000003f98ebd55f in __cxa_pure_virtual () from /usr/lib64/libstdc++.so.6
#6  0x00000000004007b6 in main ()

Не стесняйтесь спрашивать тесты или подробности в комментариях. На вопрос:

  • Это фактический код? Да! это! байт для байта. Я проверил и перепроверял.

  • Какую именно версию GnuCC вы используете?

    $ g++ --version
    g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
    Copyright (C) 2010 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
  • Можно ли увидеть сгенерированную сборку? Да, здесь он находится на pastebin.com

Ответ 1

Это ошибка, специфичная для Red Hat, не присутствующая в FSC GCC. Это не проблема в вашем коде.

В системе с CentOS 6 GCC и FSF GCC 4.4.7, которые генерируют сборку и просматривают различия между ними, выдается один бит:

CentOS 6 GCC генерирует

movq $_ZTV8BaseType+16, (%rsp)

тогда как FSF GCC 4.4.7 генерирует

movq $_ZTV11TypeTextFix+16, (%rsp)

Другими словами, один из исправлений GCC Red Hat позволяет неправильно настроить vtable. Это часть вашей функции main, вы можете увидеть ее в своем собственном списке сборок вскоре после .L48:.

Red Hat применяет много патчей к своей версии GCC, а некоторые из них - исправления, которые влияют на формирование кода. К сожалению, у одного из них есть непреднамеренный побочный эффект.

Ответ 2

Хотя истинным решением этой ошибки было бы не использовать RedHat GnuCC 4.4.7 (или любой компилятор RedHat...), мы временно застряли в этой версии.

Мы нашли альтернативу: obfuscate конструктор BaseType компилятору, тем самым предотвращая его чрезмерную оптимизацию. Мы сделали это просто, указав BaseType::BaseType() в отдельной единицы перевода.

Выполнение ошибки обхода g++. Мы действительно проверили, что оба указателя виртуальной таблицы BaseType и TypeTextFix были записаны в построенный объект перед вызовом связанных конструкторов.