【问题标题】:Byte layout of structure (#pragma pack behavior) different on MSVC vs clang/gccMSVC 与 clang/gcc 上的结构字节布局(#pragma pack 行为)不同
【发布时间】:2020-03-11 00:17:55
【问题描述】:

以下代码在 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 布局相匹配?

【问题讨论】:

  • 因为这是实现定义的行为。 C++ 标准不需要任何实现的任何特定结果。 gcc.gnu.org/onlinedocs/gcc/… 似乎表明无法更改此 gcc 行为。
  • clang 和 gcc 都在 Windows 和 Linux 上运行,这将有助于在您的问题中澄清这一点,而不是将编译器和操作系统混为一谈。还要提供目标的详细信息,因为 x64 ABI 不同于 32 位 ABI
  • 如果您在 Windows 系统上,您可以得到真正的 hacky 并向 derived_struct_container 添加一个预处理器指令,该指令将单个 char 放入结构中。也就是说,你没有。除了学术好奇心之外,还有其他目的吗?
  • 我在 Windows 中得到输出 0 1 5(g++ 9.2.0 目标 x86_64-w64-mingw32,目标 i686-w64-mingw32 相同)

标签: c++ windows clang pragma pragma-pack


【解决方案1】:

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

另外,foo 不是一个标准布局类,因为它有多个相同类型的基类子对象,所以即使没有pack,它的布局也不受任何 ABI 的约束。

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

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

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

在 gcc 中,空基类优化被具有两个相同类型基的类禁用,因此您可以通过代码更改启用它as suggested in comments under this question

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 时尝试了它,它并没有改变布局。不确定这是错误还是预期行为,但无论哪种方式,它似乎都不是问题的解决方案。

【讨论】:

  • [[no_unique_address]] 只能应用于非静态数据成员,但在示例中不能应用具有空类型的非静态数据成员。 __declspec(empty_bases) 更多相关在这里
  • @RbMm 这是 MSVC 独有的东西,对吧?我在项目符号列表中提到了它
  • 是的,这是来自您的链接。但是[[no_unique_address]]这里不能帮助,因为只影响空类型的非静态数据成员,但不影响继承。需要和一般 c++ 属性,如 __declspec(empty_bases)
  • struct E2 的建议很好,解决了问题。如果其中一个空基类也是第一个非静态数据成员类型的类型或基类,则它看起来像 empty base optimization is prohibited。我正在将旧版 Windows MSVC 代码库转换为 clang,所以这是最好的解决方案。
猜你喜欢
  • 1970-01-01
  • 2014-04-23
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 2012-01-24
  • 2016-01-30
  • 2016-08-07
相关资源
最近更新 更多