在 gcc 4.4 上"pure virtual function called",但在较新版本或 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 != '');
}
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编译时工作良好,BUT对于GnuCC文档中定义的-O1的每个标志,使用-O0 +flag编译时仍然工作良好。

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

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

这是一个特定于Red Hat的错误,在FSF 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)

换言之,Red Hat的一个GCC补丁使其无法正确设置vtable。这是main函数的一部分,在.L48:之后不久,您可以在自己的程序集列表中看到它。

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

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

我们确实找到了一个替代方案:将BaseType的构造函数模糊到编译器,从而防止其过度优化。我们只需在一个单独的翻译单元中定义BaseType::BaseType()就可以做到这一点。

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

相关内容

最新更新