-1

以下代码在 MSVC 与 clang/gcc 上的内存中生成不同的布局。为什么?

#include <stdio.h>

#pragma pack(push,1)

struct empty_struct
{
};

class derived_struct : public empty_struct
{
    int m_derivedMember;
};

class derived_struct_container : public empty_struct
{
public:
    derived_struct  m_ds;
};

class foo
{
public:
    foo() {}
    derived_struct_container m_dsc;
    int m_foo_member;
};
#pragma pack(pop)


int main()
{
    foo fb;
    printf("pf->m_dsc offset: %ld\n", (char *)&fb.m_dsc - (char *)&fb);
    printf("pf->m_dsc.m_ds offset: %ld\n", (char *)&(fb.m_dsc.m_ds) - (char *)&fb);
    printf("pf->m_foo_member offset: %ld\n", (char *)&(fb.m_foo_member) - (char *)&fb);

    return 0;
}

MSVC x64 上的输出是:

fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 0
fb.m_foo_member offset: 4

Linux下clang x64上的输出为:

fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 1
fb.m_foo_member offset: 5

如何让 clang 布局与 MSVC 布局相匹配?

4

1 回答 1

2

使用#pragma pack导致实现定义的行为。

此外,由于具有多个相同类型的基类子对象,foo它不是标准布局类pack,因此即使没有它的布局也不受任何 ABI 约束。

坦率地说,依赖非标准布局类的布局是一个糟糕的想法,而且肯定有更好的方法来实现这里的任何目标。

以下是一些不涉及更改代码的可能方法(当然,即使其中任何一个现在看起来有效,它也可能随时更改):

  • 在 Windows 中使用 clang 或 g++ 而不是 MSVC。
  • 尝试将标志传递给 MSVC 以更改 EBCO 行为,请参见此处的文章,也许可以提供 0 1 5 版本。
  • 编辑 gcc 或 clang 的源代码以构建您自己的编译器并提供所需的布局。

在 gcc 中,空基类优化被具有两个相同类型基的类禁用,因此您可以按照此问题下的评论中的建议通过代码更改来启用它:

struct empty_struct {};
struct E2 {};

class derived_struct : public E2

(其余代码与您的示例相同)。0 0 4即使没有编译指示包,这也给了我输出。我不知道任何会改变 EBCO 行为的 gcc 或 clang 标志。

这条规则的基本原理是,在标准 C++ 中,如果两个相同类型的有效指针具有相同的值,那么它们必须指向同一个对象。这两个空子对象是不同的对象,因此它们必须存在唯一的地址。MSVC 在这方面是不合格的。

在 C++20 中有一个属性[[no_unique_address]]据说放宽了这个要求,但是我在我的 g++ 9.2.0 安装中尝试了它并且它没有改变布局。不确定这是错误还是预期行为,但无论哪种方式,它似乎都不是问题的解决方案。

于 2020-03-11T00:55:37.997 回答