问题 gcc 4.4上的“纯虚函数”,但不适用于较新版本或clang 3.4


我有一个MCVE,在我的一些机器上崩溃时用g ++版本4.4.7编译但是与clang ++版本3.4.2和g ++版本6.3一起使用。

我想要一些帮助来知道它是来自未定义的行为还是来自这个古老版本的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 du的确切版本是什么?

    $ 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上


5645
2018-01-23 15:46


起源

我没有看到任何错误,甚至特别棘手,使用此代码。它应该编译并运行。如果它失败了,我会高度自信地说它是一个编译器错误。 - Igor Tandetnik
我实际上碰巧让我可以轻松访问g ++ 4.4.6并且该程序没有使用该g ++进行核心转储,因此它看起来非常像4.4.7编译器错误。 - Mark B
有趣的是,GCC 6.2编译 main 到了无操作: godbolt.org/g/UByGsC  - 这可以掩盖编译器或代码中的其他潜在错误。 FWIW我没有看到代码有什么问题。当然,编译器错误(如果有的话)也可能已被修复。 - davmac
你可以在某处转储组件输出吗? - Slava
@Slava 这里是 - YSC


答案:


这是FSF GCC中不存在的特定于Red Hat的错误。这不是代码中的问题。

在同时具有CentOS 6 GCC和FSF GCC 4.4.7的系统上,同时生成汇编列表并查看两者之间的差异,一位跳出:

CentOS 6的GCC产生

movq $_ZTV8BaseType+16, (%rsp)

而FSF GCC 4.4.7则生成

movq $_ZTV11TypeTextFix+16, (%rsp)

换句话说,Red Hat的一个GCC补丁使它无法正确设置vtable。这是你的一部分 main 功能,您可以在不久之后在自己的装配清单中看到它 .L48:

Red Hat在其GCC版本中应用了许多补丁,其中一些是影响代码生成的补丁。不幸的是,其中一个似乎有意想不到的副作用。


9
2018-01-23 19:49



我真的希望Red Hat能够像这样停止修补。 - Lightness Races in Orbit
检查一下,可以确认。 - n.m.
@LightnessRacesinOrbit我是redhat的gcc 2.96。我可以称这一个“古代”不能吗? ; @ hvd谢谢我明天早上要测试一下。 - YSC
@YSC:嘿,的确! - Lightness Races in Orbit
定义的构造函数 BaseType 在一个单独的翻译单元确实修复(事实上,隐藏)错误:两者 BaseType  和  TypeTextFix 确实写了虚拟表。非常感谢!我可以编辑您的答案以包含该黑客以供将来参考吗? - YSC


虽然这个bug的真正解决方案是不使用RedHat GnuCC 4.4.7(或任何RedHat编译器......),但我们暂时停留在这个版本中。

我们确实找到了另一种选择:混淆了构造函数 BaseType 编译器因此阻止它过度优化它。我们只是通过定义来完成它 BaseType::BaseType() 在一个单独的翻译单位。

这样做可以绕过g ++ bug。我们确实检查了两者 BaseType 和 TypeTextFix 虚拟表指针在调用其相关构造函数之前写入构造对象。


0
2018-01-25 10:08