GCC/Linker error: using extern keyword gives duplicate definition

GCC/Linker error: using extern keyword gives duplicate definition

我读到 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: 添加结论。

extern int var1=0;

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

extern int var1;

而且整个程序只能有一个定义。通常它应该在 .c 来源中。在这种情况下,extern 也是多余的:

int var1 = 0;

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

不要初始化header中的变量。

您需要在 header 中声明它,并在一个且仅一个“.c”文件中定义/初始化它。

extern 不分配内存,它只是声明 variable.So 不要像在 header file.Instead 中那样分配变量,包括这个变量声明到另一个文件并使用它。 在您的程序中,由于您已经定义变量 var1 两次(在 header 和 .c 文件中),如下所示的错误 arised.Using extern 现在将导致链接器指向同样 var1

testfile.h

extern int var1;
void testFcn(void);

testfile.c

#include "testfile.h"

int var1 = 10;
void testFcn(void){
    int var3 = var1;
}

标准规定:

If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

(C2011, 6.9.2/1-2)

因此,如果您的 header 包含

extern int var1=10;

那么包含它的每个文件都包含 var1 的(外部)定义。此外,如果它仅包含

int var1;

,并且在翻译单元中没有其他 var1 的 file-scope 声明指定它 extern,那么该翻译单元也包含 var1 的定义.如果没有指定它的声明 static,则该声明也是 外部 声明,因为外部链接是 file-scope 声明的默认值。

但标准规定:

An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), [then] somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

(C2011,6.9/5;已强调)

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

C 并没有为程序无法遵守的无数种方式指定特定的行为,因此链接器实际对此类代码执行的操作是实现细节的问题。然而,在许多情况下,如果在给定的翻译单元中存在 object 的外部定义,那么编译器将分配存储空间并在相应的 object 中将 externally-visible 符号与其相关联文件。

当链接器遇到两个或多个包含相同强符号的 object 文件时,它会面临一个难题:它使用哪个?出一些错误。有些,在某些情况下,合并符号和它们所指的 objects。

Shouldn't the linker replace the testfile reference with the object code of the very same file?

non-conforming 代码没有 "should" 或 "should not"。此外,标准规定:

In the set of translation units and libraries that constitutes an entire program, each declaration of a particular identifier with external linkage denotes the same object or function.

(C2011, 6.2.2/2)

所以不,假设链接器应该只选择在同一翻译单元中定义的 object 是不合理的,尽管可以想象确实有些人会这样做。但是,如果这就是您想要的,那么您应该使用 internal 链接声明 object —— 也就是说,使用 static storage-class 说明符声明它。在那种情况下,声明通常根本不应该出现在 header 中,因为这会给每个包含 header 的翻译单元提供它自己的变量副本,这通常是不需要的。


郑重声明,如果您想提供外部变量,那么方法是使用外部声明 ,这不是 header 中的定义文件:

my.h

extern int foo;

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

my.c

extern int foo = 0;