结构包装是否具有确定性?

Gov*_*mar 42 c padding

例如,假设我有两个等效的结构ab不同的项目:

typedef struct _a
{
    int a;
    double b;
    char c;
} a;

typedef struct _b
{
    int d;
    double e;
    char f;
} b;
Run Code Online (Sandbox Code Playgroud)

假设我没有使用任何类似的指令,#pragma pack并且这些结构在同一架构上的相同编译器上编译,它们在变量之间是否具有相同的填充?

ric*_*ici 55

编译器是确定性的; 如果不是,单独的编译是不可能的.具有相同struct声明的两个不同的翻译单元将一起工作; 由§6.2.7/ 1保证:兼容类型和复合类型.

此外,同一平台上的两个不同的编译器应该可以互操作,尽管标准不能保证这一点.(这是一个实现质量问题.)为了实现互操作性,编译器编写者就平台ABI(应用程序二进制接口)达成一致,该平台将包括如何表示复合类型的精确规范.通过这种方式,使用一个编译器编译的程序可以使用使用不同编译器编译的库模块.

但你不仅对决定论感兴趣; 您还希望两种不同类型的布局相同.

根据标准,struct如果两个类型的成员(按顺序排列)兼容,并且它们的标签和成员名称相同,则它们是兼容的.由于您的示例structs具有不同的标记和名称,即使它们的成员类型不兼容,它们也不兼容,因此您不能在需要另一个的情况下使用其中一个.

标准允许标签和成员名称影响兼容性似乎很奇怪.该标准要求结构的成员按声明顺序排列,因此名称不能更改结构中成员的顺序.那么,为什么它们会影响填充?我不知道他们做了什么编译器,但标准的灵活性基于这样的原则,即要求应该是保证正确执行所必需的最低要求.在翻译单元中不允许使用不同标记的结构别名,因此不需要在不同的翻译单元之间宽恕它.所以标准不允许这样做.(实现在struct填充字节中插入有关类型的信息是合理的,即使它需要确定性地添加填充以为这些信息提供空间.唯一的限制是填充不能放在第一个成员之前a struct.)

平台ABI可能指定复合类型的布局而不引用其标记或成员名称.在特定平台上,使用具有此类规范的平台ABI和记录符合平台ABI的编译器,您可以摆脱混叠,虽然它在技术上不正确,显然前提条件使其不可移植.

  • `struct sockaddr`和co.总是很好的例子. (3认同)

Mat*_*lia 15

C标准本身没有提及它,所以按照原则你不能确定.

但是:很可能你的编译器坚持某些特定的ABI,否则与其他库和操作系统进行通信将是一场噩梦.在这最后一种情况下,ABI通常会规定究竟包装是如何工作的.

例如:

  • 在x86_64 Linux/BSD上,SystemV AMD64 ABI是参考.这里(§3.1)对于每个原始处理器数据类型,它详细说明了与C类型,其大小和对齐要求的对应关系,并解释了如何使用这些数据来构成位域,结构和联合的存储器布局; 一切(除了填充的实际内容)是指定的和确定的.对于许多其他架构也是如此,请参阅这些链接.

  • ARM 建议将EABI用于其处理器,并且通常后面是Linux和Windows; 聚合对齐在"ARM体系结构文档的过程调用标准",§4.3中指定.

  • 在Windows上没有跨供应商标准,但VC++基本上决定了ABI,几乎任何编译器都遵守ABI; 它可以在这里找到x86_64,这里是ARM(但是对于这个问题感兴趣的部分,它只是指ARM EABI).


cma*_*ter 10

任何理智的编译器都会为这两个结构生成相同的内存布局.编译器通常被编写为完全确定的程序.需要明确而刻意地添加非决定论,而且我不能看到这样做的好处.

然而,这并没有让你投了struct _a*一个struct _b*,并同时通过访问其数据.Afaik,即使内存布局相同,这仍然会违反严格的别名规则,因为它允许编译器通过struct _a*with访问重新排序访问struct _b*,这将导致不可预测的,未定义的行为.


Dav*_*aim 8

他们在变量之间有相同的填充吗?

在实践中,他们大多喜欢拥有相同的内存布局.

理论上,由于标准没有说明如何在对象上使用填充,因此您无法在元素之间的填充上进行任何假设.

此外,我甚至看不出为什么你想知道/假设结构成员之间的填充.只需编写标准的,兼容的C代码,你就可以了.

  • 如果struct layout不是确定性的,那么就不可能提供带有头文件进行开发的编译二进制库.(至少不涉及结构的情况.) (3认同)
  • _"我甚至看不出为什么你想知道/假设一个结构成员之间的填充"_有时你可能希望使用`struct`来完全匹配网络数据包的布局,例. (3认同)

Fra*_*e_C 5

您无法在不同系统上确定性地接近C语言中的结构或联合的布局.

虽然很多时候看起来不同编译器生成的布局是相同的,但是你必须考虑这些情况是由编译器设计的实际和功能方便所决定的,在标准的程序员选择自由的范围内,因而不是有效.

C11标准ISO/IEC 9899:2011,与先前标准几乎没有变化,在第6.7.2.1节结构和联合说明符中明确说明:

结构或联合对象的每个非位字段成员以适合其类型的实现定义方式对齐.

甚至最糟糕的是位域的情况,程序员需要有很大的自治权:

实现可以分配足够大的任何可寻址存储单元来保持位域.如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中.如果剩余的空间不足,则是否将不适合的位域放入下一个单元或重叠相邻单元是实现定义的.单元内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的.未指定可寻址存储单元的对齐.

只需计算"实施定义"和"未指定"这两个术语出现在文本中的次数.

同意检查编译器版本,机器和目标架构每次运行之前使用在不同系统上生成的结构或联合是无法承受的,你应该得到一个体面的答案你的问题.

现在让我们说是的,有一种方法.

需要明确的是,它不是解决方案,但是在不同系统之间共享数据结构交换时,您可以找到一种常见的方法:在值1上打包结构元素(标准字符大小).

使用包装和精确的结构定义可以产生足够可靠的声明,可以在不同的系统上使用.打包强制编译器删除实现定义的对齐,从而减少由于标准而导致的最终不兼容性.此外,避免使用位域,您可以删除残留的实现依赖不一致.最后,由于缺少对齐而导致的访问效率可以通过在元素之间手动添加一些虚拟声明来重新创建,以这种方式制作,以强制正确对齐每个字段.

作为一个剩余的案例,你必须考虑一些编译器添加的结构端填充,但由于没有相关的有用数据,你可以忽略它(除非动态空间分配,但你可以再次处理它).