【问题标题】:Is the alignment requirement for incomplete `struct X` and `struct Y` the same?不完整的`struct X`和`struct Y`的对齐要求是否相同?
【发布时间】:2017-09-15 07:52:24
【问题描述】:

"C: When is casting between pointer types not undefined behavior?" 的答案表明通过没有更严格对齐要求的指针来回转换是安全的。

即使标准规定的类型不完整,对齐也很重要:

指向对象或不完整类型的指针可以转换为指向不同类型的指针 对象或不完整类型。如果结果指针未正确对齐 为了 指向类型,行为未定义。否则,当再次转换回来时, 结果应与原始指针比较。当指向对象的指针是 转换为指向字符类型的指针,结果指向的最低寻址字节 物体。结果的连续递增,直到对象的大小,产生指针 到对象的剩余字节。

现在的问题是struct X 的正确对齐要求是什么。当然,它是否完整取决于它的内容,但如果它不完整(仅声明为struct X;)怎么办?或者换一种说法,我们可以从struct X* 转换为struct Y* 并返回并再次获得相同的指针吗?

如果完整的struct X 和不完整的对齐要求不同,那会不会很复杂?我无法提出它们之间的实际转换,但可能是您在struct X 完整和不完整的上下文之间的翻译之间传递了struct X*。或者这是一个始终允许的特殊情况?

【问题讨论】:

  • 首先,你必须解释一个不完整的结构甚至可以有对齐要求——一个不完整的结构不能被创建。它只能由可以访问结构完整定义的代码创建,然后可以将完整结构的地址传递给定义不完整的其他代码。
  • @AndrewHenle 奇怪的是,标准要求结果指针正确对齐指向的类型,即使该类型不完整。因此,即使对于不完整的类型,似乎也存在某种对齐要求。
  • "奇怪的是,标准要求生成的 pointer 正确对齐......" 指针的对齐限制不是任何指针的对齐限制它可能指向的结构。
  • @AndrewHenle 我将其解释为指针的值必须正确对齐,而不是存储。要求指针的存储与其指向的内容对齐没有多大意义。
  • 这不是问题。对于不完整的类型,您几乎无能为力,只需 声明 一个指向它的指针。指针的赋值是踏板踩到金属并且对齐开始起作用的地方,C编译器不会让你在不知道完整类型的情况下这样做。

标签: c pointers alignment language-lawyer


【解决方案1】:

首先,我认为您不能谈论不完整类型的对齐要求,因为对齐要求仅针对完整类型定义:

完整的对象类型具有对齐要求,这对可以分配该类型对象的地址进行了限制。 (§6.2.8/1;所有标准引用均取自 n1570.pdf,实际上是 C11)

但是在一个有效的程序中,一个指针必须指向一个完整类型(或 NULL)的对象。因此,即使指针的引用类型在某些翻译单元中不完整,该指针所引用的对象(如果有的话)也必须具有完整的类型,因此需要对齐。但是,在引用类型不完整的翻译单元中,无论是编译器还是编写代码的人都无法知道对齐要求是什么。

因此,可以合法地为指向不完整类型的指针分配非 NULL 值的唯一方法是从类型完整的某个地方(可能在另一个翻译单元中)复制指向相同类型的指针。这不是转换,因为这两种类型是兼容的,并且保证可以工作。类似地,指向不完整类型的指针的值的少数合法用途之一是将其传递给在类型完整的上下文中指向相同类型的指针。 (其他用途包括将其与另一个指针值比较是否相等或将其转换为整数并打印出来,但这些不涉及转换为不同的指针类型。)

因此,总而言之,指向不完整类型的指针在预期的用例中是有用且可精确使用的——其中引用的类型是不透明的——但不能可移植地转换为任何其他类型的指针,除了可能符合条件的 char*void*

该标准不保证将指针转换为指向对齐可能更严格的类型的指针的可能性。如果引用类型的对齐方式未知,则目标对齐方式不能更严格的指针只有char*void*。因此,任何指向不完整类型的指针到char*void* 以外的类型的任何转换都必须被视为不可移植。

确实,引用类型不完整这一事实并不重要。该标准没有指定复合类型的对齐方式。这样的对齐必须足以让编译器正确对齐成员,但它可以任意大。换句话说,不能保证类型:

typedef char oneChar; 
struct oneChar_s { char x; };
union  oneChar_u { char x; };

具有相同的对齐方式。这两种复合类型很可能具有比 1 更大的对齐方式。因此不能保证可以将 oneChar* 转换为 oneChar_s*(当然,除非 oneChar* 是由以前的相反方向的转换),并且可移植程序不会尝试。从这个意义上说,struct oneChar_s 的定义是否可见并没有区别。

并非巧合,该标准并不保证所有对象指针的大小都相同。基本理论是,在某些体系结构上,普通指针不足以精确地引用单个字节,但有可能通过添加例如位偏移量来增加指针。实际上,可能还有其他可以打包成单词的小对象,这也需要增强的指针表示,但精度低于位偏移。

在这样的架构中,不可能为小型复合对象利用不同的指针精度,因为标准坚持认为复合指针最多有两种表示形式,一种用于structs,一种用于@ 987654333@s:

所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。所有指向联合类型的指针都应具有彼此相同的表示和对齐要求。 (§6.2.5/27) [注 2]

特别是,这意味着指向给定类型对象的指针具有相同的表示,无论该类型是否完整。

指针表示的差异并不是转换为更受约束的对齐方式可能失败的唯一原因。例如,实现可能会在转换后插入代码来验证对齐(可能是为了响应清理编译器选项)。在类型不完整的情况下,编译器将无法插入静态代码来进行检查(尽管可能会进行某种运行时检查),但编译器可能会省略验证代码这一事实不会改变未定义性结果。


对于它的价值,OP 中的标准引用是针对 C99;在 C11 中,稍作修改(添加强调以表示更改的措辞):

指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐 referenced 类型,则行为未定义。否则,当再次转换回来时,结果将等于原始指针。当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续增量,直到对象的大小,产生指向对象剩余字节的指针。 (§6.3.2.3/7)

在我看来,这种变化纯粹是编辑性的。它源于更改 §6.2.5/1 中“对象类型”定义的决定。在 C99 中,存在三种类型:对象类型、函数类型和不完整类型。在 C11 中,只有对象类型和函数类型两种,注释是“在翻译单元内的不同点,对象类型可能是不完整的……或完整的……”,这是一个更准确的描述。

注意事项

  1. 作为一个完全假设的示例,请考虑一台在概念上类似于 PDP-6/10 架构的机器。这是一个字地址大的机器;一个单词大到足以包含两个地址(假设的 LISP 实现可以利用这一事实,以便将由 carcdr 字段组成的 cons 节点存储到一个单词中)。因为希望有效地表示小对象的向量,机器还具有可以提取或覆盖字中的位域的指令,其中位域指针由伴随偏移和长度信息的字指针组成。 (因此,一个字只能包含一个位域指针。)(硬件有一条指令可以通过将长度添加到偏移量并在必要时移动到从下一个字的 0 开始的位域来增加位域指针。)

    所以可能存在三种不同的指针类型:

    • 由地址和位域偏移/长度组成的全字字符指针。
    • 一个普通的半字指针,指向任何字对齐的对象类型。
    • 一个扩充的半字加一位指针,指向一个字对齐对象的指针,由地址和地址是在字的前半部分还是后半部分的指示组成。 (这种表示也可能需要一个全字,但编码更简单。但可能还有其他地方可以放置额外的位。这是一个假设的示例,请记住。)

    在这个假设的架构中,转换规则变得相当复杂。例如,您可以将char** 转换为int*,因为int 的对齐方式与char* 的对齐方式相同。但是您不能将int** 转换为int*,因为int 的对齐方式大于int* 的对齐方式。

    比起记住这些复杂的规则,程序员可能会选择简单地避免执行那些保证可移植的指针转换,即通过char*void* 往返。

  2. 即使没有必要,指向所有组合的指针也可以使用更大、更精确的指针类型。在我看来,如果不是所有复合对象,一个实现更有可能只是选择对structs 施加最小对齐。该标准的措辞将允许实现使用structs 的最小对齐和所有unions 的增强指针表示。

【讨论】:

  • 以上引用中的粗体字“引用”是什么意思?是“结果”的意思吗?这是标准在没有真正定义的情况下使用术语的地方(众多地方之一)吗?
  • @supercat: 6.2.5 第 20 段:“指针类型可以派生自函数类型或对象类型,称为引用类型......”
  • @supercat:我用粗体表示,因为它是 C99 的措辞变化,其中单词是“指向”。恕我直言,精确定义的“引用”更加清晰。
  • 还不错;我倾向于使用术语“目标类型”,因为过去分词“引用”通常用于指代在文本中引用的东西。标准定义是否指定指针的“引用”类型是指针指向的对象类型?
  • @supercat:我刚刚在评论中引用了定义!它是派生指针类型的类型。 IOW,如果你有一个指针类型 X*,它的引用类型是 X。“指向”是不令人满意的,因为类型不指向任何东西,并且值可能不指向对象(因为它可能是 NULL )。
猜你喜欢
  • 2016-02-16
  • 2018-07-29
  • 2020-11-17
  • 2018-08-25
  • 1970-01-01
  • 1970-01-01
  • 2016-05-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多