GCC /链接器错误:使用extern关键字提供重复定义

时间:2016-09-20 13:28:52

标签: c gcc linker extern

我读到here extern关键字可以与初始化结合使用,初始化将根据C标准转换为实际定义。

首先,我无法在当前C11 standard(草案)中找到定义此特定条件的实际段落。页面158ff仅提供没有初始化的示例。

此外,当我尝试编译以下内容时:

testfile.h

extern int var1=10;
void testFcn(void);

testfile.c

#include "testfile.h"
void testFcn(void){
    int var3 = var1;
}

的main.c

#include "testfile.h"
void main(void){
    testFcn();
}

..我的编译器(gcc / 5.4.1)警告我以下内容:

testfile.h:1:12: warning: ‘var1’ initialized and declared ‘extern’
 extern int var1=10;
            ^
In file included from testfile.c:1:0:
testfile.h:1:12: warning: ‘var1’ initialized and declared ‘extern’
 extern int var1=10;
            ^

链接器抛出一个错误,确认定义有重复:

/tmp/ccE8M7S0.o:(.bss+0x0): multiple definition of `var1'
/tmp/cc7OrQEI.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status

我理解编译器警告但不理解链接器错误。 链接器不应该将 testfile 引用替换为同一文件的目标代码吗?我知道如何以更好的方式实现它(即仅在源文件中定义对象),但我想理解为什么这种特定的安排不起作用。

以下讨论的结论:

我的主要困惑是我希望预处理器和链接器将这种信息传递给彼此传递某些对象定义的信息。现在我意识到这是无稽之谈,但我认为链接器应该从预处理器获得变量var testfile.h 中定义的信息。换句话说,链接器应该合并这两个定义。但这就是static关键字的用途。 感谢所有帮助解决问题的人。

Edit1 :将初始化值更改为10,因为初始化为0似乎会分散实际问题的注意力。并指出以不同的方式做到这一点肯定是解决问题的方法,但我想先完全理解它。

Edit2 :添加结论。

4 个答案:

答案 0 :(得分:2)

标准规定:

  

如果对象的标识符声明有文件   范围和初始化程序,声明是外部的   标识符的定义。

     

具有文件的对象的标识符声明   没有初始化程序的范围,也没有存储类说明符   或者使用存储类说明符static构成暂定   定义。如果翻译单元包含一个或多个暂定单元   标识符的定义,以及翻译单元包含的   没有该标识符的外部定义,那么行为   就像翻译单元包含文件范围一样   声明该标识符,复合类型为   翻译单元的结尾,初始化程序等于0。

(C2011,6.9.2 / 1-2)

因此,如果您的标题包含

extern int var1=10;

然后包含它的每个文件都包含var1的(外部)定义。此外,如果它只包含

int var1;

,并且var1没有其他文件范围声明在翻译单元中指定extern,然后该翻译单元还包含var1的定义。如果没有声明指定它static,那么该声明还是一个外部声明,因为外部链接是文件范围声明的默认声明。

但标准规定:

  

外部定义是一个外部声明   也是函数的定义(不是内联函数)   定义)或对象。如果用外部声明的标识符   连接用于表达式(除了作为一部分的一部分)   结果为sizeof_Alignof运算符的操作数   整数常量),[然后]整个程序中的某个地方   应该是标识符的一个外部定义;   否则,不得超过一个

(C2011,6.9 / 5;重点补充)

因此,如果您将一个变量的外部定义放在头文件中(如您的示例中所示),并将该头包含在对同一程序或库有贡献的多个源文件中,那么您违反了标准。

C没有为程序无法符合的无数方式指定特定的行为,因此链接器实际上对这些代码的作用是实现细节的问题。但是,在许多情况下,如果给定转换单元中存在对象的外部定义,则编译器将分配存储并在相应的对象文件中将外部可见符号与其关联。

当链接器面对包含相同强符号的两个或多个目标文件时,它有一个难题:它使用哪个?有些错误。在某些情况下,有些人会合并符号和它们所引用的对象。

  

链接器是否应该使用同一文件的目标代码替换testfile引用?

没有"应该"或"不应该"关于不符合规范的代码。此外,该标准规定:

  

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

(C2011,6.2.2 / 2)

所以不,假设链接器应该只选择在同一个翻译单元中定义的对象是不合理的,尽管可以想象确实有些人会这样做。但如果那是你想要的,那么你应该用内部链接声明对象 - 也就是说,用static存储类说明符声明它。在这种情况下,声明通常不应出现在标题中,因为这会使包含标题的每个翻译单元都有自己的变量副本,这通常不需要。

对于记录,如果要提供外部变量,那么这样做的方法是在头文件中使用外部声明,而不是定义

my.h

extern int foo;

与一个源文件中的定义相结合,例如:

my.c

extern int foo = 0;

答案 1 :(得分:1)

extern没有分配内存,它只是声明变量。所以不要像在头文件中那样分配变量。相反,将此变量声明包含在另一个文件中并使用它。 在您的程序中,由于您已经定义了变量var1两次(在头文件和.c文件中),因此出现了错误。使用如下所示的extern现在将导致链接器指向相同的{{1 }}

testfile.h

var1

testfile.c

extern int var1;
void testFcn(void);

答案 2 :(得分:0)

extern int var1=0;

这是一个定义,但您的标题必须包含声明:

extern int var1;

整个计划必须只有一个定义。通常它应该属于.c来源。在这种情况下,extern也是多余的:

int var1 = 0;

此外,默认情况下,全局变量用零初始化,因此您可以跳过该初始化程序。

答案 3 :(得分:0)

不要在标题中初始化变量。

您需要在标题中声明它,并在一个且只有一个'.c'文件中定义/初始化它。