我有2个模块(.c文件)和一个.h头文件:
file1.c中:
#include <stdio.h>
#include "global.h"
int main()
{
i = 100;
printf("%d\n",i);
foo();
return 0;
}
file2.c中
#include <stdio.h>
#include "global.h"
void foo()
{
i = 10;
printf("%d\n",i);
}
global.h
int i;
extern void foo()
当我做gcc file1.c file2.c时一切正常,我得到了预期的输出。现在,当我将头文件中的变量'i'初始化为0并再次编译时,我得到一个链接器错误:
/tmp/cc0oj7yA.o:(.bss+0x0): multiple definition of `i'
/tmp/cckd7TTI.o:(.bss+0x0): first defined here
如果我只是通过头文件中的初始化(即gcc file1.c)编译file1.c(删除对foo()的调用),一切正常。发生了什么事?
答案 0 :(得分:41)
您描述了3种情况:
.c
个文件,标题中包含int i;
。.c
个文件且int i=100;
(或任何其他值;无关紧要)。.c
文件和int i=100;
。在每种情况下,想象插入.c
文件的头文件的内容和编译成.c
文件的.o
文件,然后将这些文件链接在一起。
然后发生以下事件:
因为已经提到的“暂定定义”而工作正常:每个.o
文件都包含其中一个,因此链接器显示“ok”。
不起作用,因为两个.o
文件都包含一个带有值的定义,该值会发生冲突(即使它们具有相同的值) - 可能只有一个具有任何给定名称的{ {1}}在给定时间链接在一起的文件。
当然是有效的,因为你只有一个.o
文件,所以没有碰撞的可能性。
恕我直言,干净的事情将是
.o
或extern int i;
放入头文件int i;
)放入int i = 100;
。在这种情况下,在程序开始时使用此初始化,并且可以省略file1.c
中的相应行。 (此外,我希望命名只是一个例子;请不要在实际程序中将任何全局变量命名为main()
。)答案 1 :(得分:31)
不要在标头中初始化变量。将声明放在标题和初始化中的c
个文件中。
在标题中:
extern int i;
在file2.c中:
int i=1;
答案 2 :(得分:9)
您不应在头文件中定义全局变量。您可以在头文件中将它们声明为extern
,并在.c
源文件中定义它们。
(注意:在C中,int i;
是一个暂定的定义,如果找不到该变量的其他定义,它会为变量分配存储空间(=是一个定义)。)
答案 3 :(得分:2)
此问题的currently-accepted answer是错误的。 C11 6.9.2 / 2:
如果翻译单元包含一个或多个临时定义, 标识符,并且转换单元不包含该标识符的外部定义,那么该行为就好像该转换单元包含该标识符的文件范围声明,且复合类型截至转换单元末尾,且初始化程序等于到0。
因此,问题中的原始代码的行为就好像file1.c
和file2.c
的末尾都包含行int i = 0;
,由于多个外部定义,导致未定义的行为(6.9 / 5 )。
由于这是一个语义规则而不是约束条件,因此不需要诊断。
这里还有两个有关正确代码的问题,且答案正确:
答案 4 :(得分:1)
不要在头文件中定义varibale,在头文件中做声明(良好实践)..在你的情况下它是有效的,因为多个弱符号..阅读弱弱符号....链接:http://csapp.cs.cmu.edu/public/ch7-preview.pdf
这种类型的代码在移植时会产生问题。
答案 5 :(得分:1)
@glglgl已经解释了为什么你要做的事情不起作用。实际上,如果你的目标是在头文件中定义一个变量,你可以使用一些预处理器指令:
file1.c中:
#include <stdio.h>
#define DEFINE_I
#include "global.h"
int main()
{
printf("%d\n",i);
foo();
return 0;
}
file2.c中:
#include <stdio.h>
#include "global.h"
void foo()
{
i = 54;
printf("%d\n",i);
}
global.h:
#ifdef DEFINE_I
int i = 42;
#else
extern int i;
#endif
void foo();
在这种情况下,i
仅在您定义DEFINE_I的编译单元中定义,并且在其他地方声明。链接器不会抱怨。
在标题中声明枚举之前,我已经看过几次,下面是包含相应标签的char **的定义。我理解为什么作者更喜欢在标题中使用该定义而不是将其放入特定的源文件中,但我不确定实现是否如此优雅。