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

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

and

extern int f() {return 0;}

编译正常,没有来自gcc的警告。我使用gcc -Wall -ansi;它甚至不接受//注释。

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

在后一种情况下,我不明白为什么标准设计师选择用多余的关键字乱扔语法。

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


当前回答

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 Linkages of identifiers [...] 4 For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,23) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration. If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage. 5 If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern. If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

其他回答

The extern keyword informs the compiler that the function or variable has external linkage - in other words, that it is visible from files other than the one in which it is defined. In this sense it has the opposite meaning to the static keyword. It is a bit weird to put extern at the time of the definition, since no other files would have visibility of the definition (or it would result in multiple definitions). Normally you put extern in a declaration at some point with external visibility (such as a header file) and put the definition elsewhere.

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 Linkages of identifiers [...] 4 For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,23) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration. If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage. 5 If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern. If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

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

这就是为什么十年后:

像Coccinelle这样的代码转换工具倾向于在函数声明中标记extern以便删除; 像git/git这样的代码库遵循这个结论,并从其代码中删除extern(适用于git 2.22, 2019年第二季度)。

参见刘丹顿(Denton- l)的commit ad6dad0, commit b199d71, commit 5545442(2019年4月29日)。 (由Junio C Hamano - gitster -在commit 4aeeef3中合并,2019年5月13日)

*.[ch]: remove extern from function declarations using spatch There has been a push to remove extern from function declarations. Remove some instances of "extern" for function declarations which are caught by Coccinelle. Note that Coccinelle has some difficulty with processing functions with __attribute__ or varargs so some extern declarations are left behind to be dealt with in a future patch. This was the Coccinelle patch used: @@ type T; identifier f; @@ - extern T f(...); and it was run with: $ git ls-files \*.{c,h} | grep -v ^compat/ | xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place


但这并不总是那么简单:

参见刘丹顿(Denton- l)提交的7027f50(2019年9月04日)。 (由Denton Liu—Denton- l—在2019年9月5日提交7027f50中合并)

compat/*.[ch]: remove extern from function declarations using spatch In 5545442 (*.[ch]: remove extern from function declarations using spatch, 2019-04-29, Git v2.22.0-rc0), we removed externs from function declarations using spatch but we intentionally excluded files under compat/ since some are directly copied from an upstream and we should avoid churning them so that manually merging future updates will be simpler. In the last commit, we determined the files which taken from an upstream so we can exclude them and run spatch on the remainder. This was the Coccinelle patch used: @@ type T; identifier f; @@ - extern T f(...); and it was run with: $ 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 has some trouble dealing with __attribute__ and varargs so we ran the following to ensure that no remaining changes were left behind: $ git ls-files compat/\*\*.{c,h} | 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年Q4)中,任何虚假的extern都被删除了。

参见Emily Shaffer (nasamuffin)的commit 65904b8(2019年9月30日)。 帮助:Jeff King (peff)。 参见刘丹顿(Denton- l)提交的8464f94(2019年9月21日)。 帮助:Jeff King (peff)。 (由Junio C Hamano—gitster—在commit 59b19bc中合并,2019年10月7日)

从函数声明中删除extern 在这个文件的创建过程中,每次引入一个新的函数声明时,都会包含一个extern。 然而,从5545442(*。[ch]:使用spatch从函数声明中删除extern, 2019-04-29, Git v2.22.0-rc0),我们一直在积极尝试防止在函数声明中使用extern,因为它们是不必要的。 移除这些虚假的外部。

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

与没有声明extern的常规函数不同,它可以在任何源文件中定义(但不能在多个源文件中定义,否则会得到链接器错误,提示您已经给出了函数的多个定义),包括声明了extern的那个源文件。因此,在我们的例子中,链接器在同一个文件中解析函数定义。

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

您需要区分两个独立的概念:函数定义和符号声明。"extern"是一个链接修饰符,它是对编译器的一个提示,说明后面引用的符号定义在哪里(提示是,"不在这里")。

如果我写

extern int i;

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

extern int f() {return 0;}

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

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

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

如果您想声明和同时定义一个文件作用域变量,使用extern是错误的。例如,

extern int i = 4;

将给出一个错误或警告,这取决于编译器。

如果显式地希望避免定义变量,则使用extern非常有用。

让我解释一下:

假设文件a.c包含:

#include "a.h"

int i = 2;

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

文件a.h包括:

extern int i;
int f(void);

文件b.c包含:

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

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

The extern in the header is useful, because it tells the compiler during the link phase, "this is a declaration, and not a definition". If I remove the line in a.c which defines i, allocates space for it and assigns a value to it, the program should fail to compile with an undefined reference. This tells the developer that he has referred to a variable, but hasn't yet defined it. If on the other hand, I omit the "extern" keyword, and remove the int i = 2 line, the program still compiles - i will be defined with a default value of 0.

文件作用域变量是隐式定义的,如果没有显式地为它们赋值,则默认值为0或NULL——这与在函数顶部声明的块作用域变量不同。extern关键字避免了这种隐式定义,因此有助于避免错误。

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