extern关键字对C函数的影响

时间:2009-05-13 07:51:53

标签: c syntax standards

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

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

extern int f() {return 0;}

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

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

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

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

10 个答案:

答案 0 :(得分:127)

我们有两个文件,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)
{
   while (! 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被修改,如果这看起来不够实用。

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

希望有所帮助:)

答案 1 :(得分:75)

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

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

答案 2 :(得分:20)

您需要区分两个独立的概念:函数定义和符号声明。 “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;
}

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

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

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

答案 3 :(得分:14)

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)如果先前声明指定internal或   外部链接,后面的声明中的标识符的链接是相同的   在先前声明中指定的联系。如果没有先前的声明可见,或者如果先前声明   声明指定没有链接,那么标识符有外部链接。

     

5 如果函数的标识符声明没有存储类说明符,则其链接   确定与存储类说明符extern声明的完全相同。如果   对象标识符的声明具有文件范围,没有存储类说明符,   它的联系是外部的。

答案 4 :(得分:7)

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

对于非内联函数,默认情况下不需要extern

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

答案 5 :(得分:3)

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

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

它没有效果的原因是因为在链接时链接器尝试解析extern定义(在您的情况下为extern int f())。只要找到它就可以在同一个文件或不同的文件中找到它。

希望这能回答你的问题。

答案 8 :(得分:1)

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

所以,代码:

    int f() {return 0;}

编译器将视为

    extern int f() {return 0;}

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

阅读更深入的解释

答案 9 :(得分:0)

  

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

这就是十年后的原因:

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

  

*.[ch]:使用spatch

从函数声明中删除extern      

有人试图从函数声明中删除extern

     

为Coccinelle捕获的函数声明删除“ extern”的某些实例。
  请注意,Coccinelle在处理__attribute__或varargs函数时遇到一些困难,因此一些extern声明将留在以后的补丁中处理。

     

这是使用的Coccinelle贴片:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);
     

和它一起运行:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place