C++ C++;使用Apple';s LLVM编译器,但不与g++-7.2.0

C++ C++;使用Apple';s LLVM编译器,但不与g++-7.2.0,c++,clang,llvm-clang,virtual-inheritance,C++,Clang,Llvm Clang,Virtual Inheritance,更新:我已经创建了一个甚至更多的M,但仍然是CVE的复制崩溃。摘要:删除了Base类中Bool*bools\uu字段的所有用法(但仍必须定义它,否则不会发生崩溃)。还从Base及其子体中删除了Base::Initialize()和虚拟方法规则。已附上新的MCVE 我已经为这段代码创建了一个MCVE,并将其发布在下面 一些描述性细节:代码使用虚拟基类和派生类,某些实例化的派生类具有调用从“基类”继承的非虚拟方法的构造函数(实际上是派生类,但在继承层次结构中高于我所称的“派生”类)来初始化“基类”类

更新:我已经创建了一个甚至更多的M,但仍然是CVE的复制崩溃。摘要:删除了
Base
类中
Bool*bools\uu
字段的所有用法(但仍必须定义它,否则不会发生崩溃)。还从
Base
及其子体中删除了
Base::Initialize()
和虚拟方法
规则
。已附上新的MCVE

我已经为这段代码创建了一个MCVE,并将其发布在下面

一些描述性细节:代码使用虚拟基类和派生类,某些实例化的派生类具有调用从“基类”继承的非虚拟方法的构造函数(实际上是派生类,但在继承层次结构中高于我所称的“派生”类)来初始化“基类”类数据。该方法调用在派生类中重写的虚拟方法。我意识到这是一个危险的事情,但是从我(可能有限的)对C++的理解来看,它似乎应该工作,因为派生类构造函数的主体直到“Basic”类虚拟表被设置才执行。在任何情况下,segfault都不会在调用“基类”的初始化方法期间发生

segfault发生在“基类”构造函数中,并且仅当构造函数的主体为空时发生。如果我将调试行添加到构造函数中,以便在到达该点时打印出来,则会打印出调试行,代码会正常运行。我的猜测是,出于某种原因,编译器正在优化掉应该在“基类”构造函数体执行之前发生的初始化,包括vtable的设置

正如主题行所说,当使用Apple的g++或g++7.2.0进行编译而不进行优化时,该代码运行良好,并且当使用g++7.2.0进行编译时,甚至可以运行良好。它只有在使用苹果的LLVM实现的g++编译
-O2
-O3
时才会出错。该编译器的
g++--version
输出为:

% /usr/bin/g++ --version

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Content
接下来是MCVE

#include <iostream>

using namespace std;

class OriginalBaseClass {
public:
  OriginalBaseClass(long double data1 = 1, long int data2 = 1) : data1_(data1), data2_(data2) { cout << "In OriginalBaseClass constructor\n"; }
private:
  long double data1_;
  long int data2_;
};

class Base : public virtual OriginalBaseClass {
public:
  Base(long int data1 = 0, long int data2 = 0) : data1_(data1), data2_(data2) { cout << "In Base constructor\n"; }
  virtual ~Base();
private:
  bool* bools_;
  long int data1_;
  long int data2_;
};

Base::~Base()
{
  cout << "In Base destructor\n";
}

class Derived_A : public virtual Base {
public:
  Derived_A() { cout << "In Derived_A constructor\n"; }
};

class Derived_B : public Derived_A {
public:
  Derived_B() : OriginalBaseClass(), Base(4, 1), Derived_A() { cout << "In Derived_B constructor\n"; }
};

int main()
{
  Derived_B Derb;
}
#包括
使用名称空间std;
类原始类{
公众:

OriginalBaseClass(long double data1=1,long int data2=1):data1_U1(data1),data2_U2(data2){cout这看起来像是由于无效生成未对齐的SSE存储而导致的一个错误。下面是一个基于代码的最小示例:

struct alignas(16) Base1 { };

struct Base2 : virtual Base1 {
    __attribute__((noinline)) Base2() : data1_(0), data2_(0) { }

    long dummy_, data1_, data2_;
};

struct Base3 : virtual Base2 { };

int main() { Base3 obj; }
这是由Clang生成的布局(GCC使用相同的布局):

我们可以看到,
Base3
Base1
合并,因此它们共享地址。
Base2
Base3
实例化,然后以8字节偏移量放置,将
Base2
实例对齐8字节,即使
alignof(Base2)
为16。这仍然是正确的行为,因为这是
Base2
中所有成员字段的最大对齐方式。从虚拟基类
Base1
继承的对齐方式不需要保留,因为
Base1
由负责对齐
Base1
的派生类
Base3
实例化>对

问题在于Clang生成的代码:

mov    rbx,rdi ; rdi contains this pointer
...
xorps  xmm0,xmm0
movaps XMMWORD PTR [rbx+0x10],xmm0
Clang决定使用一条要求16字节对齐的
movaps
指令初始化
data1
data2
,但
Base2
实例仅对齐8字节,导致segfault

看起来Clang假设它可以使用16字节对齐的存储,因为
alignof(Base2)
是16,但是这种假设对于具有虚拟基的类是错误的

如果需要临时解决方案,可以使用
-mno SSE
标志禁用SSE指令的使用。请注意,这可能会影响性能


安腾ABI文档可在以下位置找到:

它明确提到nvalign:

nvalign(O):对象的非虚拟对齐,即 没有虚基的O的对齐

然后有一个关于如何分配的解释:

虚拟基地以外的成员分配

如果D不是空基类或D是数据成员:从偏移量开始 dsize(C),必要时递增,以便对齐到nvalign(D),以便 基类或对齐数据成员的(D)。将D放置在此偏移处 除非这样做会导致两个组成部分(直接或间接)的 具有相同偏移量的相同类型。如果该组件类型 发生冲突时,将候选偏移量增加nvalign(D)作为基准 类或按数据成员的对齐(D)进行排序,然后重试,直到 成功发生(不迟于sizeof(C)四舍五入至 所需的校准)

看起来Clang和GCC都尊重安腾ABI,使用非虚拟对齐方式正确对齐
Base2
。我们也可以在上面的记录布局转储中看到这一点


您可以使用
-fsanitize=undefined
(GCC和Clang)编译程序,以在运行时获得此误报警告消息:

main.cpp:29:5: runtime error: constructor call on misaligned address 0x7ffd3b895dd8 for type 'Base2', which requires 16 byte alignment
0x7ffd3b895dd8: note: pointer points here
 e9 55 00 00  ea c6 2e 02 9b 7f 00 00  01 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  f8 97 95 34

目前有三个bug。我已经报告了所有bug:

  • Clang未对齐的movaps错误:
  • 克朗的消毒液错误:
  • GCC的消毒剂缺陷:

我建议您使用调试器找出导致SEGFULT的一般问题区域,然后自己调试,或者将其提取到较小的程序中,成功崩溃并共享,以便我们可以帮助您调试。嗨,谢谢。除了Xcode中的调试器外,还有其他调试器可以处理使用Apple g++编译的代码吗?我已经找到了尝试在Xcode中使用该构造函数,但它只是停留在“基类”构造函数上。它甚至不会进入其中-这就是我怀疑编译器本质上是在优化构造函数的原因之一。尝试使用-fsanize=address编译并运行程序。它在clang和gcc中都可用。此工具
main.cpp:29:5: runtime error: constructor call on misaligned address 0x7ffd3b895dd8 for type 'Base2', which requires 16 byte alignment
0x7ffd3b895dd8: note: pointer points here
 e9 55 00 00  ea c6 2e 02 9b 7f 00 00  01 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  f8 97 95 34