【问题标题】:Memory-layout compatibility between C and C++C 和 C++ 之间的内存布局兼容性
【发布时间】:2015-11-18 07:24:50
【问题描述】:

我正在构建一个 C++ 库,它使用许多函数,structC 库中定义。为了避免将任何代码移植到 C++,我将典型的条件预处理添加到 C 头文件中。例如,

//my_struct.h of the C library
#include <complex.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
  double d1,d2,d3;
#ifdef __cplusplus
  std::complex<double> z1,z2,z3;
  std::complex<double> *pz;
#else
  double complex z1,z2,z3;
  double complex *pz;
#endif
  int i,j,k;
} my_struct;

//Memory allocating + initialization function
my_struct *
alloc_my_struct(double);

#ifdef __cplusplus
}
#endif

alloc_my_struct() 的实现是用 C 编译的。它只是通过malloc() 分配内存并初始化my_struct 的成员。

现在,当我在 C++ 代码中执行以下操作时,

#include "my_struct.h"
...
  my_struct *const ms = alloc_my_struct(2.);

我注意到*ms 始终具有预期的内存布局,即任何访问,例如ms-&gt;z1,都会计算为预期值。考虑到(如果我错了,请纠正我)分配期间my_struct 的内存布局由 C 编译器(在我的情况下为 gcc -std=c11)决定,而在 C++ 编译器访问期间(在我的案例g++ -std=c++11)。

我的问题是:这种兼容性是否标准化?如果没有,有什么办法可以解决吗?

注意:我没有足够的知识来反对对齐、填充和其他实现定义的细节。但值得注意的是,由 C 编译的 GNU 科学库正在实现相同的方法(尽管它们的 structs 不涉及 C99 复数)以用于 C++。另一方面,我已经进行了充分的研究,得出的结论是 C++11 保证了 C99 double complexstd::complex&lt;double&gt; 之间的布局兼容性。

【问题讨论】:

  • @Robᵩ: CMIIW = 如果我错了,请纠正我
  • 这个问题更多的是关于complex而不是struct

标签: c++ c


【解决方案1】:

C 和 C++ 共享内存布局规则。在这两种语言中,结构都以相同的方式放置在内存中。即使 C++ 确实想做一些不同的事情,将结构放在 extern "C" {} 中也可以保证 C 布局。

但是您的代码正在执行的操作依赖于 C++ std::complex 和 C99 complex 是否相同。

请看:

【讨论】:

  • 对于 op 使用 gcc,_Complex 应该足够好了。 gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Complex.html 。但我猜 C++ 代码需要用 -std=gnu++11 编译
  • 即使保证complex布局相同,也不能保证整个my_struct结构布局完全相同。
  • @AlexLop。如果这是真的,那意味着 C 和 C++ 之间绝对没有互操作性。相反,C++ 标准花费了大量精力来强制 POD 类型的内存布局,即使不打包结构,C/C++ 互操作性也几乎可以保证。出于同样的原因,不少使用struct 的Linux 系统调用在C 和C++ 中都可以使用,例如stafs man7.org/linux/man-pages/man2/statfs.2.html
  • @AlexLop。这是一个实现间的互操作性问题,不是吗?
  • @AlexLop。不同的编译器有时会以不同的方式填充结构。但这几乎总是因为诸如最佳 64 位对齐之类的原因。我敢打赌,您的案例在 32 位编译中有一个 8 字节对象,并且 ICC 决定将其填充到 8 个字节,即使在 32 位中它不是必需的。这样做会使结构在 64 位和 32 位模式下看起来相同。编译器有时也会将结构填充到 8 字节对齐,以便更好地适应 CPU 缓存行。我相信 ICC 会为英特尔做好准备。
【解决方案2】:

您的程序具有未定义的行为:您对 my_struct 的定义在词法上并不相同。

你在赌对齐、填充和其他各种东西在两个编译器之间不会改变,这已经够糟糕了……但既然这是 UB,即使它是真的,任何事情都可能发生!

【讨论】:

  • 关于词汇相同的规则仅适用于语言内,而不适用于语言间。 C++ 标准规定该结构在其他 C++ 翻译单元中必须相同,但不能与 C 翻译单元相同(因为这超出了 C++ 标准的范围,所以不能这么说)。如果结构是标准布局类型,那么实际上它应该在与 C 共享时工作正常,并且 libstdc++ 确保 std::complex&lt;float&gt; 的布局与 C complex float 相同。
【解决方案3】:

它可能并不总是相同的!

在这种情况下,sizeof(std::complex&lt;double&gt;) 看起来与 sizeof(double complex) 相同。 还要注意编译器可能(或可能不会)根据优化配置向结构添加填充以使它们与特定值对齐。并且填充可能并不总是相同,从而导致不同的结构大小(在 C 和 c++ 之间)。

相关帖子的链接:

C/C++ Struct memory layout equivalency

我会添加特定于编译器的属性来“打包”这些字段, 从而保证所有整数都是相邻且紧凑的。这是 更少关于 C 与 C++,更多关于您可能使用的事实 用两种语言编译时有两个“不同”的编译器,即使 这些编译器来自一个供应商。

添加一个构造函数不会改变布局(虽然它会使 类非 POD),但添加访问说明符,如 private 之间 这两个字段可能会改变布局(实际上,不仅在 理论)。

C struct memory layout?

在 C 语言中,允许编译器为每个 原始类型。通常对齐是类型的大小。但 它完全是特定于实现的。

引入了填充字节,因此每个对象都正确对齐。 不允许重新排序。

可能每个远程现代编译器都实现了#pragma pack,它 允许控制填充并将其留给程序员遵守 与 ABI。 (不过,这完全是非标准的。)

来自 C99 §6.7.2.1:

12 结构或联合对象的每个非位域成员都是对齐的 以适合其类型的实现定义的方式。

13 在结构对象中,非位域成员和单元 位域所在的地址按顺序增加 在其中声明它们。指向结构对象的指针,适当地 转换后,指向其初始成员(或者如果该成员是 位域,然后到它所在的单元),反之亦然。 结构对象内可能有未命名的填充,但在其 开始。

【解决方案4】:

通常,C 和 C++ 具有兼容的结构布局,因为布局由平台的 ABI 规则决定,而不仅仅是由语言决定,并且(对于大多数实现)C 和 C++ 在类型大小、数据方面遵循相同的 ABI 规则布局、调用约定等。

C++11 甚至定义了一个新术语,standard-layout,这意味着该类型将具有与 C 中类似类型兼容的布局。这意味着它不能使用虚函数,私有数据成员、多重继承(以及其他一些东西)。 C++ 标准布局类型应该与等效的 C 类型具有相同的布局。

正如其他答案中所述,您的特定代码通常不安全,因为 std::complex&lt;double&gt;complex double等效类型,并且不能保证它们是布局兼容的。 然而 GCC 的 C++ 标准库确保它能够正常工作,因为 std::complex&lt;double&gt;std::complex&lt;float&gt; 是根据底层 C 类型实现的。 GCC 的 std::complex&lt;double&gt; 没有包含两个双精度数,而是有一个 __complex__ double 类型的单个成员,编译器实现与等效的 C 类型相同。

GCC 这样做是为了支持像您这样的代码,因为这样做是合理的。

因此,将 GCC 对 std::complex 的特殊努力与 standard-layout 规则和平台 ABI 相结合,意味着您的代码将适用于该实现。

这不一定可移植到其他 C++ 实现。

【讨论】:

【解决方案5】:

另请注意,malloc() 带有 C++ 对象的结构 (std::complex&lt;double&gt;) 您跳过了 ctor,这也是 UB - 即使您希望 ctor 为空或只是将值设为零并且无害地被跳过,您如果这坏了,不能抱怨。所以你的程序工作纯属运气。

【讨论】:

    猜你喜欢
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-11
    • 1970-01-01
    • 2011-01-06
    • 1970-01-01
    相关资源
    最近更新 更多