多文件中的外部变量声明和可能的重复定义问题

10

我将以下代码一起编译运行,命令为:gcc A.c B.c -o combined

程序A:

#include<stdio.h>
int a=1;
int b;
int main()
{
extern int a,b;
fun();
printf("%d %d\n",a,b);
}

程序 B:

int a;
int b=2;
int fun()
{
printf("%d %d\n",a,b); 
return 0;
}

运行“combined”程序时,输出结果为:

1 2
1 2

现在,我有几个疑问:

  1. 为什么输出不是:

    0 2

    1 0

  2. a和b不是被定义了两次吗?

请清晰地解释这些问题,我一直很难理解extern,并且这些疑问经常出现。

提前感谢。


1
你试图欺骗编译器,而编译器却欺骗了你。 - Peter Miehle
7
重点不在于欺骗编译器,而在于理解概念。 - tapananand
4个回答

5
一个变量可以被声明多次,只要这些声明与彼此和定义一致即可。它可以在许多模块中声明,包括定义它的模块,甚至在同一个模块中多次声明。
外部变量也可以在函数内部声明。在这种情况下,必须使用extern关键字,否则编译器将认为它是局部变量的定义,其作用域、生命周期和初始值都不同。这个声明只在函数内部可见,而不是整个函数模块。
现在让我再次重申extern的定义,它说“外部变量是在任何函数块之外定义的变量”(请仔细阅读BOLD中给出的单词)。所以对于Programe Aa有定义,但b只是声明,所以extern将寻找' b '的定义,该定义在Programe B中给出。因此,从Programe A打印出1 2。现在让我们谈谈Programe B,它对a进行了声明并对b进行了定义,因此它从programe A打印出a的值,并从当前文件打印出b的值。

据我所知,int a; 既是声明又是定义,在任何函数之外它也会初始化为 0。请考虑以下示例:int main() { int a; //既是声明又是定义... int a = 5; //编译错误:a 的定义重复。 }你也可以尝试在全局变量中实现同样的效果。 - tapananand
1
@TapanAnand 默认初始化发生在定义不可用的情况下。但在您的情况下,ab都已经声明并正确定义,因此编译器不会再次对它们进行初始化,这是为了避免多重定义错误。 - Dayal rai
好的,所以在编程 A 中 int b; 可能不是初始化,但它必须是定义,那么为什么在编程 B 中 int b = 2 不会引起错误? - tapananand
1
@TapanAnand int b程序A 中并不是初始化,而只是声明。外部变量在程序启动时被分配和初始化,内存仅在程序结束时释放。当程序启动时,它会立即找到 程序B 中给出的 b 的定义。 - Dayal rai

4
所以,很久以后我回答了自己的问题。虽然这个说法:" int b; 是一个声明, int b = 2; 是定义"是正确的,但是每个人给出的原因都不清楚。
如果没有 int b = 2; int b; 就是一个定义,那么有什么区别?
区别在于链接器处理多个符号定义的方式。有弱符号和强符号的概念。
汇编器将此信息隐式地编码到可重定位目标文件的符号表中。函数和初始化的全局变量得到强符号。未初始化的全局变量得到弱符号。
因此,在程序A中, int a = 1 是强符号,而 int b; 是弱符号,在程序B中, int b = 2 是强符号,而 int a 是弱符号。
鉴于强符号和弱符号的概念,Unix链接器使用以下规则来处理多个定义符号:
  1. 不允许多个强符号。
  2. 给定强符号和多个弱符号,请选择强符号。
  3. 给定多个弱符号,请选择任何一个弱符号。
因此,现在我们可以讨论上述情况发生了什么。
  1. int b = 2; int b; 之间,前者是强符号,而后者是弱符号,所以b被定义为值2。
  2. int a = 1 int a 之间,a被定义为1(同样的推理)。
因此,输出结果为1 2

2
因为这里的变量不是定义了两次,只是声明了两次。函数从变量定义中获取值而不是变量声明中获取。
声明引入标识符并描述其类型。通过声明,我们向编译器保证该变量或函数已在程序的其他地方定义,并将在链接时提供。例如,声明如下: extern int a; 定义实际上是实例化/实现此标识符。定义如下: int a=5;int a; 可查阅此链接以获取更多参考信息。
还有一个很好的stackoverflow帖子extern告诉编译器变量在外部定义,因此它会查找外部函数,然后在程序A中找到int a=1和在程序B中找到int b=2
对于AUTO变量: int a;//同时是定义和声明 要了解更多存储类别知识,请点击此链接int a在main函数或任何其他函数之外是声明(即全局),在任何函数内部都被称为定义。

据我所知,int a; 既是声明又是定义,在任何函数外部,它实际上也成为了初始化值为0的变量。 考虑以下代码: int main() { int a; //既是声明又是定义... int a = 5; //会出现错误,a被多次定义。 } 您也可以尝试在全局变量中使用相同的方法。 - tapananand
是的,我知道它们之间的区别,但我的意思是写int a;既声明又定义,即为a分配内存。在任何函数之外,它还将其初始化为一个明确定义的值(0),而不是垃圾值。 - tapananand
抱歉晚了,有电力问题...你所说的可能对于自动变量是正确的,但对于外部变量来说并非如此,只需查看我回答中的链接即可。 - 0decimal0
@TapanAnand 在主函数或其他任何函数之外的 int a 是声明;只有在任何函数内部才被称为定义。 - 0decimal0
2
@TapanAnand 抱歉,我没有注意到你的例子---考虑:int main() { int a; //声明和定义都在这里... int a = 5; //出错了,a被多次定义。 } 对于AUTO变量来说,定义和声明是相同的,但对于EXTERN变量来说不是。 - 0decimal0

1
据我所知: 输出将是1 2和1 2,因为您在主函数中将a和b定义为外部变量。因此,它将尝试从其他文件中获取值。 至于第二个问题,我认为编译器正在取变量的初始化值并合并它们,因为a和b都被定义为全局变量在两个文件中。 如果两者都在函数内定义,则情况可能不同。 欢迎任何建议或其他输入。

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