【问题标题】:different type variables with same identifier linked together具有相同标识符的不同类型变量链接在一起
【发布时间】:2019-02-25 04:13:45
【问题描述】:

啊.h

void addr(void);

交流

#include <stdio.h>

int x;

void addr(void) {
    printf("a:x=%p\n", &x);
}

b.c

#include <stdio.h>
#include "a.h"

char x;

int main(void) {
    addr();                 /* a:x=0x601044 */
    printf("b:x=%p\n", &x); /* b:x=0x601044 */

    return 0;
}

为什么编译器或链接器没有抱怨两个具有不同类型和相同标识符 (x) 的 extern 声明,而是默默地链接在一起?

环境:

$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
$ gcc -o test -Wall -std=c11 a.c b.c

【问题讨论】:

  • 请添加相关的编译器、版本和编译选项
  • 初始化无变量编译,初始化一个变量编译,但初始化两个变量似乎会引发重复符号错误。也许编译器/链接器会在可能的情况下隐式解析变量类型。

标签: c gcc


【解决方案1】:

a.c 中的声明 int x;b.c 中的 char x; 只是标识符 x 的暂定定义。

C11 标准草案 N1570 规定:

6.9.2 外部对象定义
...
2 具有文件范围的对象的标识符声明没有初始化程序,并且没有存储类说明符或存储类说明符为静态,构成暂定定义.

如果您在两个文件中初始化x(类似于a.c 中的int x = 2;b.c 中的char x = '1';,它们将成为“完整”定义,然后您将遇到来自链接器的多个定义错误。

类似:

Error   LNK1169 one or more multiply defined symbols found  
Error   LNK2005 x already defined in a.obj  

【讨论】:

  • 这没有解释这种行为,因为 C 2018 6.9.2 2 说“如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,那么行为就像翻译单元包含该标识符的文件范围声明,复合类型在翻译单元的末尾,初始值设定项等于 0。但它的行为并不完全像两个单元在 x 的声明中都有 = 0。有关说明,请参阅 my answer
【解决方案2】:

简介

C 标准没有定义两次使用外部链接定义标识符的行为。某些行为通常被定义为 C 的扩展,特别是在 Unix 系统上。然而,这个扩展依赖于具有兼容类型的定义;定义int x;char x;的结果一般没有定义。

讨论

使用外部链接定义标识符两次违反了 C 标准中的约束,在 C 2018 6.9 5(加粗)中:

如果用外部链接声明的标识符在表达式中使用(不是作为sizeof_Alignof 操作数的一部分,其结果是整数常量),在整个程序的某处那里应该是标识符的一个外部定义;否则,不得超过一个。

在您的程序中,x 用于表达式&amp;x,因此适用上述约束:x 必须只有一个外部定义。当违反约束时,根据 C 2018 4 2,C 标准未定义由此产生的行为。

那么为什么int x;char x; 的行为不同于int x = 0;char x = 0;?有人可能认为它们应该是相同的,因为前者是暂定定义(因为它们没有存储类说明符或初始化程序),而 C 2018 6.9.2 2 说:

如果一个翻译单元包含一个或多个标识符的暂定定义,并且该翻译单元不包含该标识符的外部定义,则该行为与翻译单元包含该标识符的文件范围声明完全相同,带有翻译单元末尾的复合类型,初始化器等于 0。

有两个原因。第一个是关于违反约束导致 C 标准未定义的行为的规则是一个压倒一切的规则;它优先于关于暂定定义的规则。

第二个是,虽然 C 标准没有定义行为,但其他文档可能会定义它。如 C 2018 J.5.11(这是一个信息性部分而不是标准的规范性部分)中所述,C 语言的一个常见扩展是允许多个外部定义。一般来说,定义的类型应该是一致的,并且只应该初始化一个。

例如,Systems V Application Binary Interface 描述了在混合定义或混合普通和非普通定义的情况下如何协调多个定义。编译器通过生成一个目标文件来与 C 的此扩展合作,该目标文件根据标识符是否具有常规定义或只是暂定定义而对标识符进行不同的标记。例如,使用 Apple LLVM 10.0.0 和 clang-1000.11.45.5 对于 x86_64 编译包含 char x; 的文件会生成标记为公共部分的符号 x,但编译包含 int x = 0; 的文件会生成符号 x标记为一般部分。 (当nm 命令应用于编译器生成的目标文件时,它会分别为这些部分显示CS。)

总结

结果是:

  • 定义 x 两次不是 C 标准定义的。
  • 编译器和链接器扩展了 C 标准,允许x 的多个暂定定义以及最多一个常规定义。
  • 尽管有扩展,但在一个地方用int 定义x 和在另一个地方定义char 的行为是不正确的,但链接器没有诊断出来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-22
    • 2012-08-23
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    • 2020-12-09
    相关资源
    最近更新 更多