【问题标题】:GCC and G++ struct packing differences?GCC 和 G++ 结构体打包的区别?
【发布时间】:2018-01-03 10:05:15
【问题描述】:

我在我的 C++ 项目中使用 C 代码,其中包括使用 extern "C"。然后我在我的 C++ 类中定义结构,这些结构被传递给 C 代码(使用 gcc 编译)。问题是当 C 代码访问在 C++ 类中定义的结构时,我得到不同的对齐方式,并且代码读取和写入错误数据。这看起来很奇怪,但我已经调试了 3 个小时,基本上当我从 C++ 代码打印指向该结构的深层成员的指针时,一切都很好。但是如果我从 C 代码中打印指针,地址就不同了!

为什么会发生这种情况?如何确保对齐一致?

(我通过重新编译所有内容来仔细检查这不是一个过时的编译问题。仍然是相同的结果。在 C 中,地址是 C++ 代码中报告的成员地址之前的 8 个字节)

编辑:问题在于 g++ 和 gcc 会以不同的方式处理空结构。如果我的代码中有一个空的 struct foo {},那么在 g++ 下,我的 struct 的 sizeof 将大 1 个字节,并且所有成员的偏移量都会被弄乱。所以我将一个虚拟的 uint8_t 放入空结构中,这样它就不再是空的,并且代码库中的所有此类问题似乎都消失了。现在对齐似乎工作相同,结构大小也相同(对于每个空结构 g++ 之前会添加 1 个字节)。我不是 100% 确定 gcc 发生了什么,所以如果有人有任何要添加的内容,请随时填写此内容

我的 gcc 版本是:

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.5' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609

我在 gcc 下的编译标志是:

-std=gnu99 -Wchar-subscripts -Wformat -Wformat-nonliteral -Wformat-security -Wmissing-braces -Wparentheses -Wsequence-point -Wswitch -Wtrigraphs -Wno-unused-function -Wunused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wuninitialized -Wdiv-by-zero -Wfloat-equal -Wdouble-promotion -fsingle-precis
ion-constant -Wshadow -Wpointer-arith -Wwrite-strings -Wconversion -Wredundant-decls -Wunreachable-code -Winline -Wenum-compare -Wlong-long -Wchar-subscripts -Wextra -Werror -g -Os -ffunction-sections -fdata-sections -m32 -fstack-protector -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -ftest-coverage -fprofile-arcs

g++

-std=c++11 -Wextra -Werror -g -Os -ffunction-sections -fdata-sections -m32 -fstack-protector -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -ftest-coverage -fprofile-arcs

Edit2:在我的 C 标志中添加了 -pedantic,以使 gcc 对空结构发出警告。之前删除它是因为它破坏了许多其他东西和第三方标头(我们必须使用 -Werror)。幸运的是,宏 ({}) 语句之类的东西可以以 __extension__ 为前缀,以使它们通过 -pedantic。在代码库中还有其他一些需要更改的东西。还不知道在启用 pedantic 的情况下是否难以实现。有谁知道如何只对空结构启用警告或禁用一些迂腐不允许的特定事物?

【问题讨论】:

  • 向我们展示您现在在做什么。然后准确解释您对它的期望以及您得到的结果。
  • 用 g++ 编译的 cpp 文件:printf("%p", &data.substruct.foo) -> 00a79e08。然后调用 cfunction_dosomething(&data)。然后在用 gcc 编译的文件中: cfunction_dosomething(struct data *self) -> printf("%p", &self->substruct.foo) -> 00a79e00
  • 哦,来吧。 1800 代表,你在 cmets 中发布代码?
  • 您应该将相关代码等放在问题中而不是评论中。更多的上下文会有所帮助。试着把它变成一个最小的、完整的可验证的例子,因为缺少太多东西,无法真正理解发生了什么。
  • 将解决方案放在答案中,而不是作为对问题的编辑。

标签: c gcc g++ alignment


【解决方案1】:

正确的解决方案似乎是永远不要在将跨 C++ -> C 边界使用的代码中定义空结构。我有一个:

struct spinlock { } 

在针对单个处理器目标构建时为空。所以 g++ 会制作这个 1 字节,而 gcc 会丢弃它。因此,我的结构将具有不同的大小,并且 offsetof 甚至会返回不同的结果,具体取决于代码是使用 gcc 还是 g++ 构建的。

所以我改成

struct spinlock { uint8_t dummy; }

现在我的结构完美对齐,一切正常,无需任何特殊的 C++ 标准规范。

【讨论】:

    【解决方案2】:

    空结构为您提供了一些实现定义的或非标准的行为。首先,标准 C 不支持空结构。GCC 支持它们作为大小 == 0 的扩展。

    C++ 支持空结构,但要求它们在用作“最派生对象”时具有非零大小。但是,C++ 还允许从空结构继承的类/结构来优化子类的大小。

    【讨论】:

    • 你知道如何让它警告空结构而不必使用-pedantic吗? (迂腐打破了开关范围之类的东西,基本上所有其他优雅的扩展)
    • 真正令人不安的是 gcc 和 g++ 之间的行为不同。如果它是相同的,那么当包含空结构的东西在 c++ 中定义然后传递给 c 函数时,代码至少可以正常工作。当事情以这种方式不同时,它会创建非常难以找到溢出。如果该结构在 c++ 文件中声明,C 代码会认为空结构之后的所有数据与实际位置完全不同。
    • 因为它在没有 -pedantic 的情况下很好地通过了编译,所以在大多数情况下一切都会正常工作,因为 C++ 结构总是会更大,如果只有 C 代码对结构数据进行操作,什么都不会破坏。直到您尝试在 C++ 中设置结构成员并将结构传递给底层 C 代码进行处理,后来才发现 C 代码看到完全不同的图片移动了 1、2、3 或更多字节。
    • 我不知道如何禁用或警告 C 零大小的结构扩展。您是对的,应该有一种方法可以禁用它(或强制它与 C++ 一样),因为它会默默地导致您遇到的不兼容问题。如果您对此打开了 GCC 错误,我怀疑他们会愿意添加这样的选项,但即便如此,也可能需要数年时间。这远不是一个理想的建议,但如果您使用的是单元测试框架,您可能会想出相当简单的测试来验证 C 和 C++ 将结构视为具有相同的大小/布局。
    猜你喜欢
    • 2010-10-14
    • 2011-08-16
    • 2017-07-16
    • 2010-09-15
    相关资源
    最近更新 更多