【问题标题】:"pure virtual function called" on gcc 4.4 but not on newer version or clang 3.4在 gcc 4.4 上“调用纯虚函数”,但在较新版本或 clang 3.4 上没有
【发布时间】:2017-06-08 04:35:40
【问题描述】:

我有一个 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)

这实际上是最小的,如果删除此代码的任何功能,它就可以正常运行。

分析

代码 sn-p 在使用 -O0 编译时可以正常工作,但是在使用 -O0 +flag 编译时,对于 -O1 的每个标志(如 GnuCC documentation 中定义的那样),它仍然可以正常工作。

生成一个核心转储,可以从中提取回溯:

(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 ()

请随时在 cmets 中询问测试或详细信息。 问:

  • 实际代码吗?是的!它是!一个字节一个字节。我已经检查并重新检查了。

  • 你使用的是什么版本的 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.
    
  • 我们能看到生成的程序集吗?是的,here it is on pastebin.com

【问题讨论】:

  • 我看不出这段代码有什么问题,甚至特别棘手。它应该编译并运行。如果它失败了,我会非常自信地说这是一个编译器错误。
  • 我实际上碰巧有 g++ 4.4.6 对我来说很容易访问,并且该程序不会使用该 g++ 进行核心转储,因此它看起来很像 4.4.7 编译器错误。
  • 有趣的是,GCC 6.2 将 main 编译为无操作:godbolt.org/g/UByGsC - 这可能会掩盖编译器或代码中的潜在错误。 FWIW 我看不出代码有什么问题。当然也有可能编译器的错误(如果有的话)已经被修复了。
  • 你能把汇编输出转储到某个地方吗?
  • @Slava here it is

标签: c++ g++ redhat undefined-behavior


【解决方案1】:

这是 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 版本应用了许多补丁,其中一些补丁是影响代码生成的补丁。不幸的是,其中一个似乎有意想不到的副作用。

【讨论】:

  • 我真希望 Red Hat 停止这样的补丁。
  • 检查了这个,可以确认。
  • @LightnessRacesinOrbit 我已经了解了 redhat 的 gcc 2.96。我可以称它为“古代”,不是吗? ; @hvd 谢谢,明天早上我会测试确认。
  • @YSC:嘿,确实!
  • 在单独的翻译单元中定义 BaseType 的构造函数确实可以修复(实际上是隐藏)该错误:BaseType TypeTextFix 虚拟表都是确实写了。非常感谢!我可以编辑您的答案以包含该黑客以供将来参考吗?
【解决方案2】:

虽然这个错误的真正解决方案是不使用 RedHat GnuCC 4.4.7(或任何 RedHat 编译器......),但我们暂时被这个版本卡住了。

我们确实找到了替代方案:将 BaseType 的构造函数混淆到编译器,从而防止它过度优化它。我们只是通过在单独的翻译单元中定义 BaseType::BaseType() 来做到这一点。

这样做可以绕过 g++ 错误。在调用相关构造函数之前,我们确实检查了 BaseTypeTypeTextFix 虚拟表指针是否都写入了构造对象。

【讨论】:

    猜你喜欢
    • 2021-12-05
    • 2011-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多