【问题标题】:Same struct with different definitions in translation units翻译单元中具有不同定义的相同结构
【发布时间】:2021-10-18 17:49:30
【问题描述】:

0.c

#include <stdio.h>

struct test{
 int a;
};

struct test get(int in);

int main(){
 struct test t = get(1234);
 printf("%d\n",t.a);

 return 0;
}

1.c

struct test{
 char a;    // Note this is char instead of int.
};

struct test get(int in){
  struct test t = {in};
  return t;
}

struct test 有两种不同的定义。一种以int 为数据类型,另一种以char 作为其数据类型。

这是未定义的行为吗? C 没有像 C++ 那样正式拥有one dentition rule,这篇文章说多个定义可以吗? Are different translation units allowed to define structures with the same name?

【问题讨论】:

  • 绝对是UB。也许是严格的别名违规?
  • 直觉上我猜如果结构有内部链接这很好,但外部链接可能会出现问题,因为链接器可能会在两个翻译单元之间生成相同的符号名称。
  • 链接的问题有什么不清楚的地方吗?那里接受的答案对我来说似乎很简单,问题中提出的情况几乎与这个相同。
  • 链接的问题说这没问题,这看起来根本不行。更具体地说,我在另一个 TU 中使用了一个 TU 的结果。
  • 在另一个 TU 中使用结果并具有两个单独的定义是完全不同的两件事。如果返回类型的定义对于定义函数的 TU 而言是本地的,则它不能在该 TU 之外使用,除非在外部定义了相同的类型并且完全兼容。

标签: c


【解决方案1】:

这是未定义的行为吗?

是的。

这篇文章说多个定义都可以吗?

当然,但不是在两个 TU 之间使用它们时。

问题甚至不在struct test t = get(1234);,而在之前,只是在函数声明struct test get(int in);。这是规则6.2.7

2 所有引用相同对象或函数的声明都应具有兼容的类型;否则,行为未定义。

来自6.7.6.3p15

对于要兼容的两种函数类型,两者都应指定兼容的返回类型 [...]

来自6.2.7p1

[...] 如果它们的标签和成员满足以下要求,则在单独的翻译单元中声明的两个结构 [...] 是兼容的:[...] 如果两者都在各自的翻译单元中的任何位置完成,则以下附加要求适用:它们的成员之间应存在一一对应关系,以便每对对应的成员都声明为兼容的类型。

文件0.cstruct test get(int in);声明函数get,文件1.cstruct test get(int in){...}声明函数get(定义也是一个声明)。两者具有相同的名称,因此它们引用相同的函数。

两者都有返回类型struct test。在0.c struct test 中有一个int 类型的成员,在1.c struct test 中有一个char 类型的成员。两个文件中两个结构的第一对成员的类型不兼容,所以类型struct test不兼容,所以函数声明不兼容,所以代码的行为是未定义的。

【讨论】:

  • 我明白了,如果每个 TU 在自己的文件中使用结构,那么应该没问题吗?确认这在 C++ 中仍然不行吗?
  • if each TU uses the structs in its own file then it should fine? 嗯,这些都是模糊的术语,比如“单独”或“使用”。您不必在main() 中使用函数get,只需声明即可使程序未定义。对于没有链接的符号(定义为内部函数的东西,如func() { int here; })和具有静态链接的符号(用static 定义)应该没问题。 will not be ok in C++?我不知道“this”指的是什么,但可以肯定的是,在 C++ 中,函数的返回类型必须在 C 中兼容。
【解决方案2】:

这是未定义的行为吗?

是的。

C 并没有像 C++ 那样正式有一个牙列规则,而这篇文章说多个定义可以吗?是否允许不同的翻译单元定义具有相同名称的结构?

C 没有与 C++ 完全相同的单一定义规则,但它确实有类似的规则。每个标识符最多可以有一个外部定义,在程序的任何地方都有外部链接。在任何给定的翻译单元中最多可以有一个具有内部链接的标识符定义。在该标识符的范围内,在同一命名空间中最多可以有一个没有链接的标识符定义,除非 typedef 可以相同地重新定义。

您提到的答案解释说,由于结构类型声明没有链接,您可以在不同的翻译单元中重复使用结构标签,但这不足以满足本问题中提供的代码的需求。

这里,为了很好地定义程序的行为,函数get() 中任何位置的所有声明都必须相互兼容(C17,6.2.7/2)。声明包括定义,这意味着get() 在其原型中0.c 中指定的类型必须与其在1.c 中的定义兼容。 “兼容”是 C 语言中定义的术语,包含在语言规范的第 6.2.7 节中,包括其他几个引用。

函数声明的规则在第 6.7.6.3/15 段中。最相关的条款是第一句话:

对于要兼容的两种函数类型,两者都应指定兼容的返回类型。

结构类型兼容性的定义我们回到6.2.7/1:

分别声明的两种结构、联合或枚举类型 如果翻译单元的标签和成员满足 以下要求:如果一个用标签声明,另一个应 用相同的标签声明。如果两者都在任何地方完成 他们各自的翻译单元,然后是以下附加 适用要求:之间应存在一一对应关系 它们的成员,使得每对对应的成员都是 用兼容的类型声明; [...] 如果这对中的一个成员是 用一个名字声明,另一个用相同的名字声明。为了 两个结构,对应的成员应该在同一个中声明 顺序。

您的两个struct test 类型(强制)使用相同的标签定义,并且它们的成员具有相同的名称以相同的顺序,但它们对应的成员没有 有兼容的类型(细节留作练习)。因此,0.c 中函数 get() 的声明与 1.c 中的定义不兼容,因此程序具有未定义的行为——即使从未调用过 get(),它仍然会发生。

【讨论】:

  • 感谢您的信息。我喜欢问与其他答案相同的问题。如果每个 TU 在自己的文件中使用结构,那么它在 C 中应该没问题,但要确认这在 C++ 中仍然不行,因为它的 ODR?
  • @Dan,对于在不同翻译单元中定义相同标签的不兼容结构类型,它不会在 C 中生成 UB。每个翻译单元可以声明和访问该 TU 中定义的任何一个的对象,而不会因此产生 UB。在 C++ 中情况并非如此,如果类类型(包括结构类型)的多个定义不都由相同的标记序列组成,则它们会产生 UB。
  • 感谢您的澄清。我也可以问这个,你提到的规则我们也涵盖了这个?:file 0.cint get(int in);,然后在file 1.cchar get(char in){ /* definition */},它们有不同的输入类型和返回类型。与您提到的原因相同,这将是 UB?
【解决方案3】:

这样的 C 代码的问题是编译器只存储函数的外部名称,而没有提供有关其参数和返回类型的信息。因此,两个翻译单元都看不到彼此的函数声明。它们只提供函数get 的外部名称。并且链接器认为函数的定义对应于两个名称get。但是声明和定义中引用的结构类型不同且不兼容。所以程序有未定义的行为。

【讨论】:

    猜你喜欢
    • 2016-03-29
    • 2012-03-11
    • 2018-06-15
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多