在 C 语言中,我没有注意到在函数声明之前使用的 extern
关键字有任何影响。起初,我认为在单个文件中定义 extern int f();
时强制您在文件范围之外实现它。但是我发现两者:
extern int f();
int f() {return 0;}
和
extern int f() {return 0;}
编译得很好,没有来自 gcc 的警告。我用 gcc -Wall -ansi
;它甚至不接受 //
条评论。
使用extern
在函数定义之前有什么影响吗?或者它只是一个可选关键字,对函数没有副作用。
在后一种情况下,我不明白为什么标准设计者选择在语法中添加多余的关键字。
编辑:为了澄清,我知道变量中有 extern
的用法,但我只是询问 函数 中的 extern
。
我们有两个文件,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 在服务信号时被修改(因此,不稳定),如果这看起来不够实用的话。
外部对象对于信号处理程序、您不想放入标头或结构中的互斥锁等非常有用。大多数编译器会优化以确保它们不会为外部对象保留任何内存,因为它们知道它们'将在定义对象的模块中保留它。然而,在对公共函数进行原型设计时,用现代编译器指定它并没有什么意义。
希望有帮助:)
据我记得标准,默认情况下,所有函数声明都被视为“外部”,因此无需显式指定。
这不会使这个关键字无用,因为它也可以与变量一起使用(在这种情况下 - 它是解决链接问题的唯一解决方案)。但是有了这些功能 - 是的,它是可选的。
您需要区分两个独立的概念:函数定义和符号声明。 "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 关键字避免了这种隐式定义,从而有助于避免错误。
对于函数,在函数声明中,关键字确实是多余的。函数声明没有隐式定义。
int i = 2
行吗?声明是否正确,看到 int i;
,编译器将为该变量分配内存,但看到 extern int i;
,编译器不会分配内存而是在别处搜索变量?
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 声明的一样。如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。
内联函数有 special rules 关于 extern
的含义。 (请注意,内联函数是 C99 或 GNU 扩展;它们不在原始 C 中。
对于非内联函数,不需要 extern
,因为它默认处于启用状态。
请注意,C++ 的规则是不同的。例如,您将从 C++ 调用的 C 函数的 C++ 声明中需要 extern "C"
,并且对于 inline
有不同的规则。
IOW, extern 是多余的,什么都不做。
这就是为什么,10年后:
像 Coccinelle 这样的代码转换工具会倾向于在函数声明中标记 extern 以便删除;
像 git/git 这样的代码库遵循该结论并从其代码中删除 extern(对于 Git 2.22,2019 年第二季度)。
请参阅 Denton Liu (Denton-L
) 的 commit ad6dad0、commit b199d71、commit 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,因为它们是不必要的。删除这些虚假的外部。
extern
关键字通知编译器该函数或变量具有外部链接 - 换句话说,它在定义它的文件之外的文件中可见。从这个意义上说,它与 static
关键字的含义相反。在定义时放置 extern
有点奇怪,因为没有其他文件可以看到定义(或者它会导致多个定义)。通常,您将 extern
放在具有外部可见性的某个点(例如头文件)中的声明中,并将定义放在其他地方。
声明一个函数 extern 意味着它的定义将在链接时解析,而不是在编译期间解析。
与未声明为 extern 的常规函数不同,它可以在任何源文件中定义(但不能在多个源文件中,否则您将收到链接器错误,说明您已经给出了函数的多个定义),包括它被声明为 extern。所以,在你的情况下,链接器在同一个文件中解析函数定义。
我不认为这样做会有多大用处,但是做这样的实验可以更好地了解该语言的编译器和链接器是如何工作的。
它无效的原因是因为在链接时链接器尝试解析外部定义(在您的情况下为 extern int f()
)。不管是在同一个文件中找到还是在不同文件中找到,只要找到就行。
希望这能回答你的问题。
extern
添加到任何函数中呢?
在 C 中,函数被隐式定义为 extern
,无论关键字是否实际声明。
所以,代码:
int f() {return 0;}
编译器将视为
extern int f() {return 0;}
本质上,典型的函数定义与以 extern
关键字开头的函数定义之间没有语义差异,如本例所示。您可以在 https://www.geeksforgeeks.org/understanding-extern-keyword-in-c/ 阅读对此的更深入的解释