ChatGPT解决这个技术问题 Extra ChatGPT

extern 关键字对 C 函数的影响

在 C 语言中,我没有注意到在函数声明之前使用的 extern 关键字有任何影响。起初,我认为在单个文件中定义 extern int f();强制您在文件范围之外实现它。但是我发现两者:

extern int f();
int f() {return 0;}

extern int f() {return 0;}

编译得很好,没有来自 gcc 的警告。我用 gcc -Wall -ansi;它甚至不接受 // 条评论。

使用extern在函数定义之前有什么影响吗?或者它只是一个可选关键字,对函数没有副作用。

在后一种情况下,我不明白为什么标准设计者选择在语法中添加多余的关键字。

编辑:为了澄清,我知道变量中有 extern 的用法,但我只是询问 函数 中的 extern

根据我在尝试将其用于某些疯狂的模板目的时所做的一些研究,大多数编译器不支持 extern 的形式,因此实际上并没有做任何事情。
它并不总是多余的,请参阅我的答案。任何时候您需要在模块之间共享您不希望在公共标头中的内容时,它都非常有用。但是,将公共头文件中的每个函数“外部化”(使用现代编译器)几乎没有好处,因为他们可以自己解决。
@Ed .. 如果 volatile int foo 是 foo.c 中的全局变量,并且 bar.c 需要它,则 bar.c 必须将其声明为 extern。它确实有它的优点。除此之外,您可能需要共享一些您不希望在公共标头中公开的功能。
@Barry 如果有的话,另一个问题是这个问题的重复。 2009 年与 2012 年

T
Tim Post

我们有两个文件,foo.c 和 bar.c。

这是 foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

现在,这里是 bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   if (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

如您所见,我们在 foo.c 和 bar.c 之间没有共享标头,但是 bar.c 在链接时需要在 foo.c 中声明的内容,而 foo.c 在链接时需要来自 bar.c 的函数。

通过使用'extern',您告诉编译器在链接时将找到(非静态)后面的任何内容;在当前通行证中不要为它保留任何东西,因为它稍后会遇到。在这方面,函数和变量被同等对待。

如果您需要在模块之间共享一些全局并且不想将其放在/初始化它在标头中,这将非常有用。

从技术上讲,库公共头文件中的每个函数都是“extern”,但是将它们标记为这样几乎没有好处,这取决于编译器。大多数编译器可以自己解决这个问题。如您所见,这些函数实际上是在其他地方定义的。

在上面的例子中,main() 只会打印一次 hello world,但会继续输入 bar_function()。另请注意, bar_function() 在此示例中不会返回(因为它只是一个简单的示例)。想象一下 stop_now 在服务信号时被修改(因此,不稳定),如果这看起来不够实用的话。

外部对象对于信号处理程序、您不想放入标头或结构中的互斥锁等非常有用。大多数编译器会优化以确保它们不会为外部对象保留任何内存,因为它们知道它们'将在定义对象的模块中保留它。然而,在对公共函数进行原型设计时,用现代编译器指定它并没有什么意义。

希望有帮助:)


如果没有 bar_function 之前的 extern,您的代码将编译得很好。
@Elazar,请注意损坏的编译器。
@Elazar,我已经注意到,将函数原型设计为 extern 对于现代编译器来说是相当无用的:)
@Tim:那么您没有使用我使用的代码的可疑特权。这有可能发生。有时标头还包含静态函数定义。这是丑陋的,而且 99.99% 的时间都是不必要的(我可能会偏离一个或两个数量级,夸大了它的必要性)。它通常发生在人们误解为只有在其他源文件将使用该信息时才需要标头;标头(ab)用于存储一个源文件的声明信息,并且不希望其他文件包含它。有时,它会出于更扭曲的原因而发生。
@Jonathan Leffler - 确实很可疑!我之前继承了一些相当粗略的代码,但老实说,我从未见过有人在标题中放置静态声明。听起来你有一份相当有趣和有趣的工作,虽然:)
佚名

据我记得标准,默认情况下,所有函数声明都被视为“外部”,因此无需显式指定。

这不会使这个关键字无用,因为它也可以与变量一起使用(在这种情况下 - 它是解决链接问题的唯一解决方案)。但是有了这些功能 - 是的,它是可选的。


然后,作为标准设计者,我将不允许将 extern 与函数一起使用,因为它只会给语法增加噪音。
向后兼容可能会很痛苦。
@ElazarLeibovich实际上,在这种特殊情况下,不允许它会增加语法的噪音。
限制关键字如何增加噪音超出了我的范围,但我想这是一个品味问题。
但是,允许对函数使用“extern”很有用,因为它向其他程序员表明该函数是在另一个文件中定义的,而不是在当前文件中,也没有在包含的头文件中声明。
5
5 revs, 5 users 94% Dave Neary

您需要区分两个独立的概念:函数定义和符号声明。 "extern" 是一个链接修饰符,向编译器提示后面引用的符号的定义位置(提示是“不在此处”)。

如果我写

extern int i;

在 C 文件的文件范围内(在功能块之外),那么您说的是“变量可能在其他地方定义”。

extern int f() {return 0;}

既是函数 f 的声明,又是函数 f 的定义。在这种情况下,定义覆盖了外部。

extern int f();
int f() {return 0;}

首先是声明,然后是定义。

如果要声明并同时定义文件范围变量,则使用 extern 是错误的。例如,

extern int i = 4;

将给出错误或警告,具体取决于编译器。

如果您明确希望避免定义变量,则使用 extern 很有用。

让我解释:

假设文件 ac 包含:

#include "a.h"

int i = 2;

int f() { i++; return i;}

文件 ah 包括:

extern int i;
int f(void);

并且文件 bc 包含:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

头文件中的 extern 很有用,因为它在链接阶段告诉编译器,“这是一个声明,而不是一个定义”。如果我删除 ac 中定义 i 的行,为其分配空间并为其分配值,则程序应该无法使用未定义的引用进行编译。这告诉开发人员他已经引用了一个变量,但还没有定义它。另一方面,如果我省略了“extern”关键字,并删除了 int i = 2 行,程序仍然可以编译 - i 将被定义为默认值 0。

文件范围变量隐式定义为默认值 0 或 NULL 如果您没有显式地为它们分配值 - 与您在函数顶部声明的块范围变量不同。 extern 关键字避免了这种隐式定义,从而有助于避免错误。

对于函数,在函数声明中,关键字确实是多余的。函数声明没有隐式定义。


您是要删除 -3 段中的 int i = 2 行吗?声明是否正确,看到 int i;,编译器将为该变量分配内存,但看到 extern int i;,编译器不会分配内存而是在别处搜索变量?
实际上,如果您省略“extern”关键字,程序将无法编译,因为在 ac 和 bc 中重新定义了 i(由于 ah)。
d
dirkgently

extern 关键字根据环境采用不同的形式。如果声明可用,则 extern 关键字采用之前在翻译单元中指定的链接。在没有任何此类声明的情况下,extern 指定外部链接。

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

以下是 C99 草案 (n1256) 中的相关段落:

6.2.2 标识符的链接 [...] 4 对于使用存储类说明符 extern 在该标识符的先前声明可见的范围内声明的标识符,23)如果先前声明指定内部或外部链接,则后面声明中标识符的链接与前面声明中指定的链接相同。如果前面的声明不可见,或者前面的声明没有指定链接,则标识符具有外部链接。 5 如果函数标识符的声明没有存储类说明符,则它的链接将完全确定,就好像它是使用存储类说明符 extern 声明的一样。如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。


它是标准,还是您只是告诉我典型的编译器行为?如果是标准,我会很高兴获得该标准的链接。不过谢谢!
这是标准行为。 C99 草案可在此处获得:<open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf>。实际的标准虽然不是免费的(草案对于大多数目的来说已经足够好了)。
我刚刚在 gcc 中对其进行了测试,“extern int h();static int h() {return 0;}”和“int h();static int h() {return 0;}”都被接受警告。是只有 C99 而不是 ANSI?你能把草案中的确切部分介绍给我吗,因为这似乎不适用于 gcc。
再检查一遍。我对 gcc 4.0.1 进行了同样的尝试,但在应该出现的地方出现了错误。如果您无法访问其他编译器,也可以尝试使用 comeau 的在线编译器或 codepad.org。阅读标准。
@dirkgently,我真正的问题是使用带有函数声明的 exetrn 有什么影响,如果没有,为什么可以将 extern 添加到函数声明中。答案是否定的,没有效果,并且曾经有过使用不那么标准的编译器的效果。
u
user9876

内联函数有 special rules 关于 extern 的含义。 (请注意,内联函数是 C99 或 GNU 扩展;它们不在原始 C 中。

对于非内联函数,不需要 extern,因为它默认处于启用状态。

请注意,C++ 的规则是不同的。例如,您将从 C++ 调用的 C 函数的 C++ 声明中需要 extern "C",并且对于 inline 有不同的规则。


这是这里唯一正确且实际回答问题的答案。
V
VonC

IOW, extern 是多余的,什么都不做。

这就是为什么,10年后:

像 Coccinelle 这样的代码转换工具会倾向于在函数声明中标记 extern 以便删除;

像 git/git 这样的代码库遵循该结论并从其代码中删除 extern(对于 Git 2.22,2019 年第二季度)。

请参阅 Denton Liu (Denton-L)commit ad6dad0commit b199d71commit 5545442(2019 年 4 月 29 日)。
(由 Junio C Hamano -- gitster --commit 4aeeef3 中合并,2019 年 5 月 13 日)

*.[ch]: 使用 spatch 从函数声明中删除 extern 一直在推动从函数声明中删除 extern。删除 Coccinelle 捕获的函数声明的一些“extern”实例。请注意,Coccinelle 在处理带有 __attribute__ 或 varargs 的函数时存在一些困难,因此一些外部声明被留下以在未来的补丁中处理。这是使用的 Coccinelle 补丁:@@ type T;标识符 f; @@ - 外部 T f(...);它运行于: $ git ls-files \*.{c,h} | grep -v ^compat/ | xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

但这并不总是那么简单:

请参阅 Denton Liu (Denton-L)commit 7027f50(2019 年 9 月 4 日)。
(由 Denton Liu -- Denton-L --commit 7027f50 中合并,2019 年 9 月 5 日)

compat/*.[ch]:使用 spatch 从函数声明中删除 extern 在 5545442(*.[ch]:使用 spatch,2019-04-29,Git v2.22.0-rc0 从函数声明中删除 extern),我们从函数声明使用 spatch,但我们有意排除了 compat/ 下的文件,因为有些文件是直接从上游复制的,我们应该避免搅动它们,以便手动合并未来的更新会更简单。在最后一次提交中,我们确定了从上游获取的文件,因此我们可以排除它们并在其余部分上运行 spatch。这是使用的 Coccinelle 补丁:@@ type T;标识符 f; @@ - 外部 T f(...);它是通过以下方式运行的: $ git ls-files compat/\*\*.{c,h} | xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place $ git checkout --\compat/regex/\compat/inet_ntop.c\compat/inet_pton.c\compat/nedmalloc/\compat/obstack .{c,h} \ compat/poll/ Coccinelle 在处理 __attribute__ 和 varargs 时遇到了一些麻烦,因此我们运行以下命令以确保没有留下任何剩余更改: $ git ls-files compat/\*\*.{c, } | xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/' $ git checkout --\compat/regex /\compat/inet_ntop.c\compat/inet_pton.c\compat/nedmalloc/\compat/obstack.{c,h}\compat/poll/

请注意,使用 Git 2.24(2019 年第四季度),任何虚假的 extern 都会被丢弃。

参见 Emily Shaffer (nasamuffin)commit 65904b8(2019 年 9 月 30 日)。
帮助者:Jeff King (peff)
参见 commit 8464f94(2019 年 9 月 21 日),Denton Liu (Denton-L)
帮助者:{ 3}.
(由 Junio C Hamano -- gitster --commit 59b19bc 中合并,2019 年 10 月 7 日)

promisor-remote.h: drop extern from function declaration 在创建此文件期间,每次引入新的函数声明时,都会包含一个 extern。但是,从 5545442(*.[ch]: remove extern from function declarations using spatch, 2019-04-29, Git v2.22.0-rc0)开始,我们一直在积极尝试防止在函数声明中使用 extern,因为它们是不必要的。删除这些虚假的外部。


1
1800 INFORMATION

extern 关键字通知编译器该函数或变量具有外部链接 - 换句话说,它在定义它的文件之外的文件中可见。从这个意义上说,它与 static 关键字的含义相反。在定义时放置 extern 有点奇怪,因为没有其他文件可以看到定义(或者它会导致多个定义)。通常,您将 extern 放在具有外部可见性的某个点(例如头文件)中的声明中,并将定义放在其他地方。


R
Rampal Chaudhary

声明一个函数 extern 意味着它的定义将在链接时解析,而不是在编译期间解析。

与未声明为 extern 的常规函数不同,它可以在任何源文件中定义(但不能在多个源文件中,否则您将收到链接器错误,说明您已经给出了函数的多个定义),包括它被声明为 extern。所以,在你的情况下,链接器在同一个文件中解析函数定义。

我不认为这样做会有多大用处,但是做这样的实验可以更好地了解该语言的编译器和链接器是如何工作的。


IOW, extern 是多余的,什么都不做。如果你这样说会更清楚。
@ElazarLeibovich 我刚刚在我们的代码库中遇到了类似的情况并得出了相同的结论。所有这些通过这里的答案都可以总结在你的一个班轮中。它没有实际效果,但可能对可读性很好。很高兴在网上见到你,而不仅仅是在聚会上:)
M
Mac

它无效的原因是因为在链接时链接器尝试解析外部定义(在您的情况下为 extern int f())。不管是在同一个文件中找到还是在不同文件中找到,只要找到就行。

希望这能回答你的问题。


那么为什么允许将 extern 添加到任何函数中呢?
请不要在您的帖子中放置无关的垃圾邮件。谢谢!
S
Sean Barton

在 C 中,函数被隐式定义为 extern,无论关键字是否实际声明。

所以,代码:

    int f() {return 0;}

编译器将视为

    extern int f() {return 0;}

本质上,典型的函数定义与以 extern 关键字开头的函数定义之间没有语义差异,如本例所示。您可以在 https://www.geeksforgeeks.org/understanding-extern-keyword-in-c/ 阅读对此的更深入的解释


请更具体。
请告诉我一件事,因为在定义中,内存分配已经完成。我们知道“extern int x=4”是无效的,那么我们如何在c中的函数定义之前使用extern关键字呢?