C语言中的extern关键字是否多余?

3

我发现在某些情况下,即使没有使用extern关键字(尽管我同意它可以给读者一些有关变量的提示),我仍然可以实现所需的结果。在某些情况下,使用extern会产生不期望的结果。

xyz.h

int i;

file1.c

#include "xyz.h"
....
i=10;
....

file2.c

#include "xyz.h"
main()
{
printf("i=%d\n",i);
}

当然,这是一个庞大的项目,需要将其化简以便理解。使用"extern"关键字时,我无法得到预期的结果。实际上,采用"extern"方法后,变量i会出现链接错误。
"extern"方法的代码如下:
file1.c
int i;
main()
{
i=10;
}

file2.c

extern int i;
foo()
{
printf("i=%d\n",i);
}

这会导致链接错误。我只是想知道为什么在第一个案例和实际案例中使用关键字"extern",我们无法完成此操作。谢谢。


抱歉,file2.c文件中没有main()函数。 - Vrajendra
@Kerrek:如果您正在指的是第一种方法,那么我想告诉您它在我的情况下起作用了。使用extern方法,我得到了变量i的链接器错误。 - Vrajendra
1
嗯... 那个链接的“重复”真的是重复吗?原帖的问题是关于“避免”使用extern,而所谓的重复则是关于正确使用extern的。 - AnT stands with Russia
1
@Harsha:我不明白你如何可能在第二种方法中遇到连接器错误。一旦你修正了函数名,你的第二种方法就不再有任何连接器问题。唯一可能导致连接器错误的方式是忘记将其中一个目标文件提供给连接器。 - AnT stands with Russia
@AnT:就像我说的,这是一个由多个文件组成的项目。我为你们分解了它。 - Vrajendra
显示剩余8条评论
2个回答

7
正式地说,你的第一个程序是无效的。在头文件中定义变量,然后将此头文件包含到多个翻译单元中,最终会导致具有外部链接的相同实体的多个定义。这是 C 中的一种约束违规。
《C语言标准》 第6.9节“外部定义”第5条规定:外部定义是同时也是函数(非内联定义)或对象的定义的外部声明。如果在整个程序中使用了具有外部链接的标识符以表达式方式(除作为sizeof操作符的操作数之一且其结果是整型常量),则该标识符必须恰好有一个外部定义;否则,就不得超过一个。
在您的第一个示例中,i的定义是一个“试探性定义”(正如评论中所提到的),但是它在包含头文件的每个翻译单元的末尾变成了正规的完全的外部定义 i。因此,“尝试性”定义从“整个程序”的角度来看并没有改变任何事情。它与本质上无关的问题(除以下的小注解外)。
使您的第一个示例编译而不出现错误的原因是流行的编译器扩展,甚至在语言标准中都提到了这种扩展。
《C语言标准》第J.5节 "常见扩展"第J.5.11节 "多个外部定义"规定: 对象的标识符可能有一个或多个外部定义,可以显式使用关键字extern,如果这些定义不一致,或者有多个初始化,行为未定义(6.9.2)。
(很可能导致C中该编译器扩展的原因有一些试探性定义支持的实现上的特殊性,但在抽象语言层面上,试探性定义与此无关。)
就i而言,您的第二个程序是有效的(顺便说一句,C不再支持隐式int)。我不明白您如何会从中获得任何链接器错误。

1
在文件作用域中的int i;只是一个_试探性定义(tentative definition)_,而不是实际定义。 - too honest for this site
@Olaf:尽管如此,这个暂定的定义在两个TU中都变成了实际的定义,不是吗? - Kerrek SB
1
@Olaf:是的,但这完全不是重点。试探性定义在翻译单元结束时变为常规定义。在两个不同的翻译单元中定义相同的外部对象在C和C++中都是非法的。 - AnT stands with Russia
1
@KerrekSB:我只是试图从标准中弄清楚。实际上,我不确定它们是否引用相同的对象。如果我没记错的话,extern确实是不必要的。避免在拼写错误时出现实际定义只是一个好习惯。(我从来没有仔细考虑过标准具体说明了什么,因为我只是出于上述原因使用extern)。 - too honest for this site
@AnT:我已经修改了file2.c文件。它不再有main()函数。对此我深表歉意。 - Vrajendra
显示剩余12条评论

1

至少有两种情况下,extern 是有意义的而不是“冗余”的:

  1. 对于文件作用域的对象(不是函数),它声明具有外部链接的对象而不提供试探性定义;试探性定义会在翻译单元结束时变成完整定义,并且在多个翻译单元中定义具有相同标识符的外部链接是不允许的。

  2. 在块作用域(函数内部),extern 允许您声明和访问具有外部链接的对象或函数,而无需将标识符引入文件作用域(因此在声明的块之外不可见)。如果名称可能与文件作用域中的其他内容冲突,则这很有用。例如:

    static int a;
    int foo(void)
    {
        return a;
    }
    int bar(void)
    {
        extern int a;
        return a;
    }
    

    如果在 bar 中没有使用 extern 关键字,则 int a; 将产生一个局部变量(自动存储)。


那么,foo访问静态全局变量'a',而bar引用的是在其他文件中定义的'a'? - Vrajendra
@Harsha:没错。 - R.. GitHub STOP HELPING ICE
非常巧妙。如果我没记错的话,这有点类似于C++中的命名空间。 - Vrajendra
我可以知道为什么第一种情况下的代码对我有效吗?我的意思是,它可能适用于哪些情况? - Vrajendra
@Harsha:尽管它不是有效的C语言,但Unix链接器创建了一个保持为“common”符号的试探性定义的传统,而不是成为常规定义。这就是为什么我认为你的程序有效。如果你在使用GCC或clang,请尝试在编译器命令行中添加“-fno-common”选项并查看是否会出现错误。 - R.. GitHub STOP HELPING ICE
如果事实确实如此,我会将这些信息添加到答案中。 - R.. GitHub STOP HELPING ICE

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