【问题标题】:Is a redeclaration of an untagged structure a compatible type?未标记结构的重新声明是兼容类型吗?
【发布时间】:2018-07-04 20:45:16
【问题描述】:

出于this question 中表达的目的,我们希望这样做:

typedef struct { int a; } A;
typedef struct { struct { int a; }; int b; } B;

A *BToA(B *b) { return (A *) b; }
B *AToB(A *a) { return (B *) a; }

希望强制转换符合 C 2011 6.7.2.1 15,它表示“指向结构对象的指针,经过适当转换,指向其初始成员(或者如果该成员是位字段,则指向单元它所在的位置),反之亦然。”

由于B里面的struct { int a; }没有名字,我们就叫它A'吧。

“适当地”没有明确定义。我假设如果A * 是一个指向A' 类型对象的有效指针,那么(A *) b 会执行适当的转换,并且类似地,如果a 是指向A' 的指针,它位于@ 987654336@,那么(B *) a是一个合适的转换。

所以问题是:A * 是指向 A' 类型对象的有效指针吗?

根据6.7.6.1,如果AA' 兼容,则A *A' * 兼容。

根据6.2.7,“如果它们的类型相同,则两种类型具有兼容的类型……此外,如果它们的标签和成员满足以下要求,则在单独的翻译单元中声明的两种结构、联合或枚举类型是兼容的:如果一个用一个标签声明,另一个应该用相同的标签声明。如果两者都在各自翻译单元内的任何地方完成,则适用以下附加要求:它们的成员之间应存在一一对应关系,使得每对对应的成员都声明为兼容类型;如果该对的一个成员使用对齐说明符声明,则另一个成员使用等效的对齐说明符声明;如果该对中的一个成员声明了一个名称,则另一个成员声明为相同的名称。对于两个结构,对应的成员应该以相同的顺序声明……”

6.7.2.3 5 这些不能是同一类型:“不包含标记的结构、联合或枚举类型的每个声明都声明了不同的类型。”

由于它们不是同一类型,它们是否兼容? 6.2.7 中的文字表示,如果在单独的翻译单元中声明它们是兼容的,但它们在同一个翻译单元中。

【问题讨论】:

  • "6.2.7 中的文字说如果在单独的翻译单元中声明它们是兼容的,但它们在同一个翻译单元中。"...所以它们不兼容?我不明白这个问题。该标准规定,如果 X 或 Y 为真,则两种类型是兼容的。 X 和 Y 都不是真的。
  • @T.C.:是的,我问是因为,从字面上看,我没有看到条件满足,所以这个声明并没有断言AA' 是兼容的。但是(a)我可能弄错了。 (b) 标准中可能有其他句子涵盖这一点。 (c) 直观地说,它们应该是兼容的。如果在单独的翻译单元中,我们以相同的方式声明A'',那么它与A 兼容,并且与A' 兼容。如果AA'' 兼容,A''A' 兼容,但AA' 不兼容,那就太奇怪了。

标签: c language-lawyer


【解决方案1】:

我没有在标准中看到任何说明struct 兼容的内容,因此我会说它们不兼容。

如 6.7.2.1§6 所述,唯一可以让您在结构之间获得有限兼容性的方法是使用联合:

一个特殊的保证是为了简化联合的使用:如果联合包含 几个结构共享一个共同的初始序列(见下文),如果联合 对象当前包含这些结构之一,允许检查常见的 它们中任何一个的初始部分,任何地方的完整类型的联合声明 可见。

例如,类似

typedef struct { int a; } A;
typedef struct { union { struct { int a; }; A export; }; int b; } B;

A *BToA(B *b) { return &b->export; }
B *AToB(A *a) { return (B *) a; }

应该是安全的,但仅用于读取访问:该标准并没有真正费心指定“检查”公共初始序列的含义,但似乎将其用于“修改”。

【讨论】:

  • 请注意这个问题被标记为language-lawyer。它只是关于解释标准,而不是关于实现通常做什么。填充的存在与否无关紧要,因为允许优化编译器任意转换没有正式定义行为的代码。
  • @EricPostpischil 只有我最初的第二段与标准所说的有所不同。我已经删除了。
  • 这样,AToB 中的转换不会将指向 aB 的第一个成员的指针转换为指向 B 的指针。它将指向B 的第一个成员的成员的指针转换为指向B 的指针。
【解决方案2】:

在两种情况下结构的兼容性是相关的:

  1. 在决定是否可以将一种类型的值或指针强制转换为另一种类型的值或指针时,无需使用强制转换运算符,也不会产生诊断。请注意,为此目的,单独声明的结构即使在结构上相同也是不兼容的,但在编译单元之间传递结构或指针时,这种兼容性是无关紧要的。

  2. 在决定是否可以将一个类型的值或指向一种类型的指针安全地提供给期望另一种类型的代码。如果结构上相同的类型不被认为是兼容的,那么在编译单元之间传递未标记的结构是不可能的。编译器曾经认为结构相同的类型即使在一个编译单元中也是兼容的,并且在一种或两种类型都未标记的情况下,编译器从来没有任何好的理由这样做,但是因为标准没有强制要求这样编译器通过轻率地假设指向一种此类类型的指针不会用于访问另一种类型的成员来无意义地削弱语言已成为一种时尚。

不幸的是,在编写标准时,其作者并不认为明确规定编译器已经在做的所有明显有用的事情以及明智的编译器将继续做的事情很重要。最终结果是,过去受支持且无争议的有用构造将不可靠,除非禁用其他有用的优化。

【讨论】:

    【解决方案3】:

    正如您在问题中所述,该标准清楚明确地指出,同一翻译单元中的两个结构定义 struct { int a; } 声明了两种不兼容的类型。尽管这可能很“奇怪”。 Compilers have always followed the standard.

    这对我来说似乎是合理的行为:如果您的项目中碰巧有语义上不相关的结构,并且碰巧有一个具有相同类型的成员列表,那么您确实希望编译器拒绝两者之间的意外分配。


    回复。您问题中的代码,根据 6.7.2.1/13,

    匿名结构或联合的成员被视为包含结构或联合的成员。

    所以我会将B 的定义视为等同于:

    typedef struct { int a; int b; } B;
    

    为了进一步分析。

    【讨论】:

      猜你喜欢
      • 2012-09-24
      • 1970-01-01
      • 2013-05-02
      • 2020-01-10
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多