为什么我可以在C语言中定义两次变量?

4

我一直在测试全局变量、定义和声明,现在停在这个情况:

main.c:

#include "stdio.h"

void func(void);
int a;

int main(void) {
    a = 20;
    printf("in main: %d\n", a);
    func();
    
    return 0;
}

add.c:

#include <stdio.h>

void func(void);
int a;

void func() {
    printf("in add: %d\n", a);
}

所以在C语言中,这行代码是

int a;

声明定义都是指变量,但我们知道一个变量不能被定义多次。那么如果我们有两个定义和两个声明的a,为什么这段代码还能编译通过呢?我正在使用CLion,当我在main中按下“转到定义/声明”按钮时,它会将指针移动到add.c中的a,当我在add.c中执行相同操作时,它会返回到main.c,所以我无法理解这里发生了什么。


1
дҪ еҸҜиғҪжғіиҰҒзҡ„жҳҜдёҖдёӘеҢ…еҗ«void func(void);е’Ңextern int a;зҡ„add.hж–Ү件гҖӮ然еҗҺеңЁдҪ зҡ„main.cдёӯеҠ е…Ҙ#include "add.h"гҖӮе…ЁеұҖеҸҳйҮҸжҳҜдёҚеҘҪзҡ„гҖӮ - undefined
当你写(例如)int a;时,它是一个“常见”的符号。请参考我的回答:https://stackoverflow.com/questions/64626917/global-variables-and-the-data-section/64627070#64627070 以获取解释。 - undefined
使用GCC编译器,将main.cadd.c文件一起编译,编译命令为gcc -Wall -Wextra -g -c,然后使用gcc main.o add.o -o yourprog进行链接。最后,使用GDB调试器对yourprog可执行文件进行调试。请务必阅读您所使用的编译器和调试器的文档。 - undefined
你的程序行为因为存在两个外部标识符a的定义而变得未定义,并不意味着你可以依赖编译器拒绝该程序或者诊断出问题。"未定义"就是"未定义"。 - undefined
1个回答

13
在任何函数之外,int x;是一个“试探性定义”,一些编译器和链接器将其视为一种“合作定义”,其中一个标识符可以在多个文件中以这种方式声明,并且将导致只定义一个对象。
由于历史原因,C语言对于外部声明(函数之外的声明)的规则有些复杂 - C语言的发展是由不同的人开发和实验而来,而不是根据我们今天所拥有的知识进行设计。 定义:int x = 3;是一个定义。它既声明了标识符x,也为一个int保留了内存,并将int初始化为3。 声明:extern int x;是一个声明,但不是一个定义。它声明了标识符x,但不为其保留内存。 extern int x;x提供了外部链接,如果它出现在函数之外,int x = 3;也会提供外部链接。外部链接意味着当它们出现在不同的源文件中时,标识符的两个实例将链接到内存中的同一对象。
C标准规定,具有外部链接的标识符“应该”最多有一个定义(C 2018 6.9 5)。(如果程序中使用了该标识符,则必须有一个定义。如果在表达式中未使用,则不需要定义。)
“试探性定义”:在函数外部,int x;是一种特殊类型的声明,称为“试探性定义”。C标准规定,如果在一个翻译单元(正在编译的源文件及其包含的所有文件)中存在试探性定义且没有常规定义,将创建一个常规定义。
那么,如果违反“最多有一个定义”的规则会发生什么呢?事实是,这不是程序必须遵守的规则。当C标准说“应该”时,意思是,如果程序遵守这个规则,行为将与C标准规定的一样。如果程序违反这个规则,C标准不定义行为(C 2018 4 2)。相反,我们让编译器和链接器定义行为,如果它们的设计者选择这样做。
在编译器和链接器中,一种常见的行为(但不是唯一的可能性)是当程序违反了关于最多一个定义的规则时:
- 如果在链接时存在多个常规定义,则报告错误。 - 如果存在多个来自尝试定义的定义,但只有一个来自常规定义,将它们合并为一个定义。
这是GCC及其相关工具在GCC版本10之前的默认行为,并在C 2018标准的常见扩展的信息部分(J.5.11)中明确提到。在当前版本的GCC中,默认情况下,任何类型的多个定义都被视为错误。您可以使用命令行开关“-fcommon”请求旧的行为。

1
值得一提的是附录J.5.11(常见扩展 - 多个外部定义),许多编译器都实现了该功能。 - undefined
从C标准的角度来看,简单来说,这是未定义行为,因此对包含它的程序的行为没有任何保证。然而,特定的编译器/链接器可能选择定义一个特定的行为,就像答案中所描述的那样。由于标准提到了一种常见的解决方案,所以这种行为并不像其他未定义行为那样未知。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接