ChatGPT解决这个技术问题 Extra ChatGPT

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

c

我的问题是关于何时应该使用 C 中的 extern 关键字引用函数。

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

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

另外.. 当它是在头文件中没有关键字的默认声明时,您是否应该extern某些东西?


N
Neuron

extern 更改链接。使用关键字,函数/变量被假定在其他地方可用,解析被推迟到链接器。

函数和变量的 extern 之间存在差异。

对于变量,它不实例化变量本身,即不分配任何内存。这需要在其他地方完成。因此,如果您想从其他地方导入变量,这一点很重要。

对于函数,这只告诉编译器链接是外部的。由于这是默认设置(您使用关键字 static 来指示函数未使用外部链接绑定),因此您无需显式使用它。


那么为什么 Git 中存在相同的外部事物:一个非常流行和现代的软件检查它:github.com/git/git/blob/master/strbuf.h
K&R 没有注意到默认将函数声明为“extern”,但是这个答案解决了我的困惑!
@rsjethani 我认为这是为了使文档更加严格和格式。
也许是一个愚蠢的问题,但这与前向声明相比如何?
N
Neuron

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

借助此处的回复并在此处与几个朋友交谈是使用 extern 的实际示例。

示例 1 - 显示一个陷阱:

stdio.h

int errno;

myCFile1.c

#include <stdio.h>

// Code using errno...

myCFile2.c

#include <stdio.h>

// Code using errno...

如果 myCFile1.omyCFile2.o 已链接,则每个 c 文件都有单独的 errno 副本。这是一个问题,因为相同的 errno 应该在所有链接文件中都可用。

示例 2 - 修复。

stdio.h

extern int errno;

stdio.c

int errno;

myCFile1.c

#include <stdio.h>

// Code using errno...

myCFile2.c

#include <stdio.h>

// Code using errno...

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


问题不在于 myCFile1 和 myCFile2 模块有单独的 errno 副本,而是它们都暴露了一个名为“errno”的符号。当链接器看到这一点时,它不知道要选择哪个“errno”,因此它会以错误消息退出。
“由链接器链接”实际上是什么意思?每个人都使用这个术语,我没有找到任何定义:(
@MarcelFalliere Wiki ~ 编译器自己编译每个源文件并为每个源文件创建一个目标文件。链接器将这些目标文件链接到 1 个可执行文件。
包含防护不能防止这种确切的事情吗?
@obskyr 不,包括警卫不会防止这种情况。包含守卫只是防止同一个头文件被多次包含在一个源文件中。它不会阻止该标题出现在多个源文件中。所以你仍然会遇到多个来源定义同一个变量的问题。
q
qris

已经说过 extern 关键字对于函数是多余的。

对于跨编译单元共享的变量,您应该在头文件中使用 extern 关键字声明它们,然后在单个源文件中定义它们,而不使用 extern 关键字。为了最佳实践,单一源文件应该是共享头文件名称的文件。


@aib“功能冗余”,请查看我在蓝兄弟的回答中的评论。
如果你不想暴露头文件中的任何函数怎么办?在一个 C 文件中声明变量并在另一个 C 文件中通过 extern 访问它不是更好吗?让链接器解决问题并隐藏标题的其余部分。
N
Neuron

许多年后,我发现了这个问题。在阅读了每一个答案和评论后,我想我可以澄清一些细节......这对于通过谷歌搜索到达这里的人来说可能很有用。

这个问题是关于使用 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)中。

让我们再次将标头 my_project.H 包含在不同的 *.C 文件中以更好地理解差异。在同一个项目中,我们添加以下文件:

// --------------------------------------
// 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;

需要注意的重要功能:

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

作为 C 库一部分的函数只能在一个模块中替换,方法是仅在该模块中重新定义具有静态的原型。例如,替换任何对 malloc 和 free 的调用以添加内存泄漏检测功能。

说明符 extern 并不是函数真正需要的。当未找到 static 时,始终假定函数为 extern。

但是,extern 不是变量的默认值。通常,任何定义变量以在多个模块中可见的头文件都需要使用 extern。唯一的例外是如果一个头文件被保证包含在一个且只有一个模块中。然后,许多项目经理会要求将此类变量放在模块的开头,而不是放在任何头文件中。一些大型项目,例如视频游戏模拟器“Mame”甚至要求这些变量只出现在使用它们的第一个函数之上。


那么为什么静态函数需要定义而不是外部函数呢? (我知道这已经晚了2年,但这实际上对理解很有帮助)
如果您在第 100 行调用该函数并在第 500 行实例化它,则需要该定义。第 100 行将声明未定义的原型。因此,您在顶部附近添加原型。
N
Neuron

在 C 中,extern 隐含在函数原型中,因为原型声明了在其他地方定义的函数。也就是说,一个函数原型默认是有外部链接的;使用 extern 很好,但是是多余的。

(如果需要静态链接,该函数必须在其原型和函数头中声明为 static,并且这些通常都应该在同一个 .c 文件中)。


q
qris

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

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


在我来到这里之前,我已经阅读了 geeksforgeeks.org 的文章,但发现它写得很糟糕。除了语法和句法上的缺点外,它还使用很多词多次表达同一点,然后略读关键信息。例如,在示例 4 中,突然包含了 'somefile.h',但除了:“假设 somefile.h 具有 var 的定义”之外,什么也没说。好吧,我们“假设”的信息恰好是我正在寻找的信息。不幸的是,此页面上的答案都不是更好。
C
Chris Lutz

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


嗯,这就是通常的翻译方式:源文件编译为目标文件,然后链接。在那种情况下你什么时候不需要 extern ?您也不会使用#include 来获取函数,而是使用函数原型。我不明白你在说什么。
我最近似乎遇到了误读的问题。对于那个很抱歉。当我是 C 新手时,我会 #include "file.c" 将一个文件中的函数直接包含到另一个文件中。然后我想出了如何使用'extern'。我以为他犯了和我一样的错误。
C
Christoph

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

此规则的例外情况是在标头中定义的内联函数和变量 - 尽管在标头中定义 - 必须是翻译单元的本地(标头包含的源文件):这些应该是 static

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

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

声明函数原型 extern 实际上是不必要的。有些人不喜欢它,因为它只会浪费空间,而且函数声明已经有溢出行限制的趋势。其他人喜欢它,因为这样,函数和变量可以以相同的方式处理。


你能解释为什么“头文件中函数和变量的所有声明都应该是外部的。”吗?在我看来,默认情况下它们是外部的。
@Lane:extern 对于函数声明是可选的,但我喜欢以同样的方式对待变量和函数——至少这是我能想到的最合理的事情,因为我不完全记得我为什么开始这样做; )
总是将全局变量包含在 C 文件中不是更好的主意,这样它们就不会被包含标题的其他随机 C 文件看到。为了清楚起见,除了初始化的真实接收器之外,始终在每个全局上使用 extern ;如果它以 extern 为前缀,则在其他地方定义。
E
Eduard - Gabriel Munteanu

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

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

静态(在该 .c 文件之外不可见的普通函数)

静态内联(来自 .c 或 .h 文件的内联)

extern (在下一种标题中的声明(见下文))

[没有任何关键字](普通函数意味着使用 extern 声明访问)


如果这是默认值,为什么在声明原型时还要 extern?
@Lane:可能有点偏颇,但是我从事的每个理智项目都使用以下约定:在标头中,仅为外部函数声明原型(因此为 extern)。在 .c 文件中,可以使用普通原型来消除对特定排序的需要,但不应将它们放在标题中。
O
Otávio Décio

当您在不同的 dll 或 lib 上定义该函数时,编译器会根据链接器来查找它。典型情况是当您从 OS API 调用函数时。