如何在C中正确使用extern关键字


235

我的问题是,何时应使用externC中的关键字引用函数。

我看不到何时应该在实践中使用它。在编写程序时,我使用的所有功能都可以通过包含的头文件使用。那么,为什么extern访问头文件中未公开的内容很有用?

我可能正在考虑extern工作方式不正确,如果是这样,请纠正我。

编辑:extern当它是在头文件中没有关键字的默认声明时,您应该做些什么吗?


Answers:


290

extern”会更改链接。使用关键字,功能/变量被假定为在其他地方可用,并且解析被推迟到链接器。

函数和变量上的“外部”之间是有区别的:在变量上,它不会实例化变量本身,即不分配任何内存。这需要在其他地方完成。因此,如果要从其他位置导入变量,则很重要。对于函数,这仅告诉编译器链接是外部的。由于这是默认设置(您可以使用关键字“ static”来指示未使用外部链接绑定功能),因此无需显式使用它。


1
那么为什么在Git中也存在相同的外部问题:一个非常流行且现代的软件进行检查:github.com/git/git/blob/master/strbuf.h
rsjethani 2013年

K&R没有注意到默认将函数声明为“ extern”,但是这个答案解决了我的困惑!
acgrantrant

@rsjethani我认为这是为了使文档更加严格和格式。
acgtyrant

也许是一个愚蠢的问题,但这与前向声明相比如何?
weberc2

196

extern告诉编译器该数据在某处定义,并将与链接器连接。

在这里的答复的帮助下,并在这里与几个朋友交谈,这是使用extern的实际示例。

示例1-显示一个陷阱:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

如果将myCFile1.o和myCFile2.o链接在一起,则每个c文件都具有errno的单独副本。这是一个问题,因为应该在所有链接的文件中都提供相同的errno

示例2-修复。

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

现在,如果链接器链接了myCFile1.o和MyCFile2.o,它们都将指向同一errno。因此,使用extern解决实现。


70
问题不是myCFile1和myCFile2模块具有errno的单独副本,而是它们都暴露了一个称为“ errno”的符号。当链接器看到此消息时,它不知道选择哪个“ errno”,因此它将通过错误消息来解决。
cwick

2
“通过链接器链接”实际上是什么意思?每个人都使用这个术语,我找不到任何定义:(
Marcel Falliere

7
@MarcelFalliere 维基〜编译器编译自身每个源文件,并为每个源文件的目标文件。链接器将这些目标文件链接到1个可执行文件。
Bitterblue

1
@cwick gcc即使使用-Wall和后也没有给出错误或警告-pedantic。为什么呢 如何 ?
2015年

6
包括防护人员是否可以防止这种确切的事情发生?
obskyr

32

已经有人说过,extern关键字对于功能是多余的。

对于跨编译单元共享的变量,应在带有extern关键字的头文件中声明它们,然后在没有extern关键字的单个源文件中对其进行定义。最佳实践是,单个源文件应该是共享头文件名的文件。


@aib“功能冗余”,请检查我在bluebrother的回答中的评论。
rsjethani 2013年

如果您不想公开头文件中的任何功能怎么办?在一个C文件中声明该变量,然后在另一个C文件中通过extern访问它会更好吗?让链接程序解决问题并隐藏标题的其余部分。
ste3e 2014年

16

许多年后,我发现了这个问题。阅读完所有答案和评论后,我想我可以澄清一些细节...这对于通过Google搜索到达此处的人们可能很有用。

问题特别是关于使用“ extern”函数,因此我将忽略对全局变量使用“ extern”。

让我们定义3个函数原型:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

头文件可由主要源代码使用,如下所示:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

为了编译和链接,我们必须在调用该函数的同一源代码文件中定义“ function_2”。其他两个函数可以在不同的源代码“ .C”中定义,也可以位于任何二进制文件( .OBJ,*。LIB,*。DLL)中,而我们可能没有这些源代码。

让我们在不同的“ * .C”文件中再次包含标头“ my_project.H”,以更好地理解它们之间的区别。在同一项目中,我们添加以下文件:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

注意的重要功能:

  • 当在头文件中将函数定义为“静态”时,编译器/链接器必须在使用该包含文件的每个模块中找到具有该名称的函数实例。

  • 通过仅在该模块中用“ static”重新定义原型,可以仅在一个模块中替换C库中的功能。例如,替换对“ malloc”和“ free”的任何调用以添加内存泄漏检测功能。

  • 函数实际上并不需要说明符“ extern”。当找不到“静态”时,始终假定函数为“外部”。

  • 但是,“ extern”不是变量的默认值。通常,任何将变量定义为在许多模块中可见的头文件都需要使用“ extern”。唯一的例外是,如果保证一个文件中只有一个模块包含头文件。

    然后,许多项目经理会要求将此类变量放置在模块的开头,而不是放在任何头文件中。一些大型项目,例如视频游戏模拟器“ Mame”,甚至要求此类变量仅出现在使用它们的第一个函数上方。


那么为什么静态函数需要一个定义而不是外部函数呢?(我知道这晚了2年,但这实际上对理解很有帮助)
SubLock69 '18

2
如果在第100行调用该函数并在第500行实例化该函数,则需要定义。第100行将声明未定义的原型。因此,您将原型添加到顶部附近。
克里斯蒂安·金格拉斯

15

在C语言中,函数原型隐含“ extern”,因为原型声明了在其他地方定义的函数。换句话说,默认情况下,函数原型具有外部链接。使用'extern'可以,但是很多余。

(如果需要静态链接,则必须在函数的原型和函数头中都将其声明为“ static”,并且通常都应将它们都放在同一.c文件中)。


8

我写了一篇非常不错的关于extern关键字的文章以及示例:http : //www.geeksforgeeks.org/understanding-extern-keyword-in-c/

尽管我不同意extern在函数声明中使用是多余的。这应该是编译器设置。因此,我建议extern在需要时在函数声明中使用。


3
在我来到这里之前,我已经阅读了geeksforgeeks.org文章,但是发现它写得很差。除了语法和语法上的缺陷外,它还使用大量单词多次指出同一点,然后略读关键信息。例如,在示例4中,突然包括了“ somefile.h”,但是除了“假设somefile.h具有var的定义”外,什么都没有说。好吧,我们正在“假设”的信息恰好是我在寻找的信息。不幸的是,此页面上的答案都没有更好的选择。
Elise van Looij,

6

如果首先将程序中的每个文件编译为一个目标文件,则需要将这些目标文件链接在一起extern。它告诉编译器“此函数存在,但是它的代码在其他地方。不要惊慌。”


嗯,通常是这样完成的:源文件编译为目标文件,然后链接。在这种情况下,什么时候您不需要外部人员?您也不会使用#include来获取函数,而是使用函数原型。我不明白你在说什么。
David Thornley,2009年

我最近似乎有误读东西的问题。对于那个很抱歉。当我不熟悉C时,我将#include“ file.c”仅仅将一个文件中的功能直接包含在另一个文件中。然后我弄清楚了如何使用“外部”。我以为他在犯同样的错误。
克里斯·卢兹

4

头文件中的所有函数和变量声明都应为extern

此规则的例外是标头中定义的内联函数和变量(尽管标头中定义了),但变量必须在转换单元(标头包含在其中的源文件)中位于本地:这些应该是static

在源文件中,extern不应将其用于文件中定义的函数和变量。只需在本地定义前面加上前缀,static对共享定义不做任何事情-默认情况下,它们将是外部符号。

extern在源文件中完全使用的唯一原因是声明在其他源文件中定义并且没有提供头文件的函数和变量。


extern实际上,不需要声明函数原型。有些人不喜欢它,因为它只会浪费空间,并且函数声明已经倾向于溢出行限制。其他人喜欢它,因为这样可以对函数和变量进行相同的处理。


您能否给出一个原因,为什么“头文件中的所有函数和变量声明都应为外部”。从其他响应中可以看出,它们默认是外部的。
lillq

@Lane:extern对于函数声明是可选的,但是我喜欢以相同的方式对待变量和函数-至少那是我能想到的最合理的事情,因为我不完全记得为什么我开始这样做;)
Christoph

总是在C文件中包含全局变量,这样它们不会被其他包含标头的随机C文件看到,这不是一个更好的主意。为了清楚起见,除初始化的真实接收器外,始终在每个全局变量上使用extern;如果以extern为前缀,则在其他地方定义。
ste3e 2014年

3

在其他源文件中实际定义的函数只能在标头中声明。在这种情况下,你应该使用的extern,宣布在头中的原型。

大多数时候,您的功能将是以下功能之一(更像是一种最佳实践):

  • 静态(在.c文件之外不可见的常规功能)
  • 静态内联(来自.c或.h文件的内联)
  • extern(在下一种标题中的声明(请参见下文))
  • [无关键字](正常功能应使用extern声明进行访问)

如果这是默认设置,为什么还要在声明原型时改变?
lillq

@Lane:可能有些偏颇,但是我从事的每个理智的项目都使用以下约定:在标头中,仅为外部函数声明原型(因此为extern)。在.c文件中,可以使用普通的原型来消除对特定顺序的需要,但不应将其放在标头中。
爱德华-加布里埃尔·蒙泰努(Gabriel Munteanu),2009年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.