【问题标题】:What are the rules for linking translation units in C11?C11中链接翻译单元的规则是什么?
【发布时间】:2016-05-09 15:15:28
【问题描述】:

C11 标准讨论了标识符的链接,但没有明显讨论链接翻译单元的规则。我的问题是通过使用 clang 编译两个简单的示例提出的。

这是我的第一个示例,它有两个相同函数的声明,但类型不兼容:

//testall.c
extern char myfun(void*);

int main(){
  char a='c';
  a=myfun(&a);
}

char myfun(char*c){
  return *c;
}

然后我运行命令:$clang -std=c11 testall.c
并且clang报错:

testall.c:9:10:错误:“myfun”的类型冲突
char myfun(char*c){
^
testall.c:2:17: 注意:之前的声明在这里
extern char myfun(void*);
^
产生 1 个错误。

我理解这个错误,因为 void 指针和指向 char 的指针是不兼容的类型。
令我困惑的是,当我将两个声明分成两个不同的翻译单元,然后将它们链接到一个时,clang 不会报告任何错误:

//test.c
extern char myfun(void*);

int main(){
  char a='c';
  a=myfun(&a);
}

// mylib.c
char myfun(char*c){
  return *c;
}

然后我运行这个命令:$clang -std=c11 test.c mylib.c.
clang 编译并链接两个翻译单元而不报告任何错误或警告。

我认为链接两个翻译单元遵循第 6.2.2 节 C11 标准标识符的链接中的规则。但似乎并非如此。谁能帮我澄清一下?

【问题讨论】:

  • 没有得到诊断并不意味着代码是合法的!传统上,编译器一次只查看一个翻译单元(因此在编译时无法检测到不匹配),并且类型信息不会传递到目标文件中,因此链接器将无法检测到不匹配要么。
  • @NateEldredge 尝试一次执行一个文件,而不是在命令行中同时指定两个文件。执行gcc a.c b.c 会一步编译和链接。 C 不进行名称修改,因此函数的名称对于链接器来说看起来完全一样。现在,如果您有三个目标文件都带有myfun 怎么办?你会得到一个函数重定义错误。很明显你说的不是真的。
  • 虽然gcc a.c b.c 运行编译器和链接器,但它们是两个独立的通道,不共享目标文件以外的信息。链接器能够检测同一符号的多个定义。但在您的示例中,函数myfun 仅在mylib.c定义 一次。文件test.c 包含myfun声明,但没有定义。因此链接器没有错误。
  • 这归结为任何在调用函数之前不呈现函数原型的 C 程序都是非常糟糕的编写。同样,任何函数原型和定义不同的 C 程序都写得很糟糕。只是不要编写糟糕的代码,你就不会遇到这样的问题。
  • 感谢您的 cmets。实际上,我们正在构建一个根据 C11 标准分析 C 程序的工具。我们在实际的一些程序中发现了这样的问题。我们需要根据C11标准找出处理此类问题的“正确”方式。如果是未定义的行为,那么工具会报错;如果它是依赖于实现的,那么我们可以选择 clang 的方式。

标签: c linker c11


【解决方案1】:

这只是未定义的行为。 C11 没有提及 链接器 或它们如何组合多个翻译单元。实际上,这不是问题,因为会有一个带有 myfun() 函数声明的头文件,其中包含这两个 C 文件。

【讨论】:

  • 您的“未定义行为”是否意味着像 C11 标准一样可能发生任何事情?或者您的意思是链接器超出了 C11 标准的范围?
  • 我的意思是前者,后者也是如此。基本上,链接器(至少我用过的 Unix 和 GNU 链接器)不做任何类型检查。所以诊断并不容易。但在您的特定示例中,它也可以在所有系统上按预期工作,因为 char*void* 需要具有相同的表示。
【解决方案2】:

在您使用两个单独文件的示例中,行为未定义。这是我的“案例”,基于 C11 标准:

C11 6.2.2(4):

对于使用存储类说明符 extern ... 声明的标识符 如果没有可见的先前声明,或者如果先前的声明 声明指定没有链接,则标识符具有外部 链接。

test.c 中,myfun 具有外部链接,因为它是用extern 声明的,并且之前的声明不可见。

C11 6.2.2(5):

如果函数的标识符声明没有存储类 说明符,它的链接是完全确定的,就好像它被声明为 存储类说明符 extern。

mylib.c 中,myfun 被声明为没有存储类说明符,因此它就像是用extern 声明的,因此它在这个翻译单元中也有外部链接。

C11 6.9.1.(7)【函数定义】:

如果声明符包含参数类型列表,该列表也 指定所有参数的类型;这样的声明者也 用作以后调用同一函数的函数原型 在同一个翻译单元中

因此mylib.cmyfun 的定义也是myfun 的声明(以防您有任何疑问)。

C11 6.2.2(2):

在构成一个翻译单元和库的集合中 整个程序,每个声明一个特定的标识符 外部链接表示相同的对象或函数。

因此两个myfuns 表示相同的功能。

C11 6.2.7(2):

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

myfun 的两个声明具有不兼容的类型(我可以显示 如果你愿意,但这就是 Clang 在单文件中抱怨的原因 case.) 因此整个程序的行为是不确定的。

请注意,6.2.7(2) 不是约束,因此 Clang 不是 违反时需要发出诊断。然而, 在单个文件的情况下,存在实际的约束违规, 因为以下内容出现在“约束”标题下:

C11 6.7(4):

同一范围内引用同一对象的所有声明 或函数应指定兼容的类型。

所以在这种情况下,Clang 必须发出诊断。

【讨论】:

    猜你喜欢
    • 2010-11-09
    • 2021-05-08
    • 2016-07-27
    • 1970-01-01
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多