【问题标题】:Why doesn't the standard require that struct members be padded minimally?为什么标准不要求最少填充结构成员?
【发布时间】:2018-11-11 22:44:36
【问题描述】:

该标准似乎没有对结构成员施加任何填充要求,即使它does prohibit reordering (6.7.2.1p6)。 C 平台不进行最小填充的可能性有多大,即不只添加确保下一个成员(或同一结构的实例,如果这是最后一个成员)足够对齐所需的最小填充量输入?

标准不要求填充最小是否明智?

我问是因为缺少填充保证似乎阻止我将序列化对象可移植地表示为结构(即使我将自己限制为仅uint8_t 数组作为成员,编译器似乎被允许在两者之间添加填充他们),我发现不得不在那里使用偏移算术有点奇怪。

【问题讨论】:

  • 序列化一直是 C 语言中的一个难题。如果您将自己限制为 uint8_t,那么只需使用数组即可。如果你不这样做 - 最小的填充将无济于事。
  • 确实,标准没有指定任何关于填充的要求,编译器可能会或可能不会应用填充,但几乎每个编译器都有一个选项(pragma 或属性)来防止特定数据类型的填充.通常称为pack,即:pack(1).
  • @Groo 是的,这不是一个很好的、无需猜测的问题。但至少 cmets 帮助我了解了一些事情(谢谢大家!)(我以前从来不需要跨网络序列化结构,在我做的事情中它只是边缘重要的——让它完整,这意味着我不想花太多时间研究它)。我将简单地做到这一点(偏移量)。
  • 达尔文解决了这个问题。一个能解决这个问题的编译器不会很受欢迎。他们没有。
  • @Mawg:我敢打赌这里有些成员根本不希望讨论这样的问题。我不认为这是任何公司的草皮或任何人的拖钓,只是人们对他们真正更愿意考虑违反福音的概念和问题感到不确定/不舒服。我希望我的回答会因为类似的原因而获得相当多的反对票(更多!),并且也会因为他们的努力而谴责微软。 C 标准委员会。

标签: c padding


【解决方案1】:

C 平台不进行最小填充的可能性有多大,即不只添加确保下一个成员(或同一结构的实例,如果这是最后一个成员)足够所需的最小填充量与其类型对齐?

本质上,“额外”填充可能允许显着的编译器优化。

不幸的是,我不知道是否有任何编译器确实这样做(因此无法提供关于其发生可能性的任何估计)。

举个简单的例子,考虑一个 32 位或 64 位架构,其中 ABI 声明字符串文字和字符数组与 32 位或 64 位边界对齐。许多 C 库函数(也)由 C 编译器本身实现;参见例如these lists for GCC。编译器可以跟踪参数以查看它们是否引用字符串文字或(开头)字符数组,如果是,则替换例如strcmp() 具有优化的内置版本(以 32 位为单位进行比较,而不是一次一个字符)。

作为一个更复杂的例子,考虑一个 RISC 硬件架构,其中未对齐的字节访问比对齐的本机字访问慢。 (例如,前者可以像后者一样在硬件中实现,然后进行位移。)这样的架构可能具有要求所有结构成员字对齐的 ABI。然后,C 编译器将需要添加超过最小的填充。

传统上,C 标准委员会一直非常小心,不会将任何类型的硬件架构排除在正确实现该语言之外。

标准不要求填充最小是否明智?

过去,C 标准的目的是确保 C 代码在使用不同的编译器编译时会以相同的方式运行,并允许在任何功能足够强大的硬件架构上实现该语言。从这个意义上说,标准不要求最小填充是非常明智的,因为某些 ABI 可能出于某种原因需要的不仅仅是最小填充。

随着 Microsoft "extensions" 的引入,C 标准的目的发生了重大转变,将 C 绑定到 C++ 以确保 C++ 编译器可以编译 C 代码,与 C++ 编译的差异最小,并提供能够以“更安全”的形式进行营销,其实际目的是使开发人员分道扬镳并将他们绑定到单个供应商实施。因为这有悖于标准的先前目的,而且像 fscanf_s() 这样标准化单供应商函数而不像 getline() 这样的多供应商函数标准化显然是不明智的,它可能无法在 C 标准的上下文中定义 sensible 的含义。它绝对不符合“良好的判断”;它现在可能指的是“被感官感知”。

我之所以问,是因为缺少填充保证似乎使我无法将序列化对象可移植地表示为结构

您一遍又一遍地犯 C 程​​序员所犯的错误。结构适合表示序列化对象。由于 C 结构规则,您不应使用结构来表示网络对象或文件头。

相反,您应该使用简单的字符缓冲区,以及访问函数(从缓冲区中提取或打包每个成员或字段)或转换函数(将缓冲区内容转换为结构,反之亦然)。

即使是像提问者这样有经验的程序员仍然更喜欢使用结构的根本原因是访问器/转换涉及大量额外代码;让编译器来做会更好:更少的代码,更简单的代码,更容易维护。

我同意。如果引入了一个新的关键字,比如serialized_struct,它甚至会非常简单;引入与传统 C 结构完全不同的成员规则的序列化数据结构。 (请注意,这种支持根本不会影响例如链接,因此它实际上并不像人们想象的那么复杂。)可以使用其他属性或关键字来指定显式字节顺序,编译器将为我们完成所有转换细节,以编译器认为最适合其编译器的特定体系结构的任何方式。这种支持仅适用于新代码,但对于减少互操作性问题将大有裨益——而且会使许多序列化代码更简单!

不幸的是,当您结合 C 标准委员会对添加新关键字的传统厌恶,以及从互操作性到供应商锁定的总体方向转变时,根本没有机会将此类内容包含在 C 标准中。

当然,如 cmets 中所述,有很多 C 库实现了一种或其他序列化方案。我什至自己写了一些(不过,对于相当特殊的用例)。一个明智的方法(双关语不好)是选择一个充满活力的(维护良好,图书馆周围有一个活跃的社区)并使用它。

【讨论】:

  • 嘿,似乎有些人真的不喜欢关于 C 标准委员会提出的事实。我不介意不同意,但如果您这样做,而不仅仅是投反对票,请花时间解释您不同意的地方,无论是通过评论还是提出自己的答案。原因是,否则无法确定否决票是否有依据(如此答案中存在错误),或者仅仅是因为某人不喜欢该答案。前者很有用,后者只是噪音。
  • 不同意“C 标准的目的已发生重大转变,将 C 绑定到 C++ 以确保 C++ 编译器可以编译 C 代码,与 C++ 编译的差异最小”。如果有的话,C 和 C++ 现在的距离比以往任何时候都大。 C 标准从来没有任何 C++ 兼容特性。
  • 另外,抱怨可选的 C11 Annex K 与此问题的主题没有任何关系
  • @M.M:C11 是朝着与 C99 完全不同的方向迈出的一步。它没有编纂现有的实践,而是创建了全新的定义,从原子到附件 K。在 C11、GCC、Intel CC、Pathscale 和 PGI C 编译器都实现了__sync_ 原子运算符之前。 C11 使用 C++ 内存顺序语义添加了原子类型支持,而以前没有任何 C 编译器支持。
  • 这是一个重复使用现有工作的案例;不是语言兼容性。已经有人为原子设计了一个定义良好的内存模型,因此 C 工作组不妨使用它,而不是重新发明轮子。顺便说一句,这仍然与结构填充无关,所以不是论坛
猜你喜欢
  • 2012-10-03
  • 2016-09-18
  • 2011-11-05
  • 1970-01-01
  • 2019-03-27
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多