extern关键字对C函数的影响

extern关键字对C函数的影响,c,syntax,standards,C,Syntax,Standards,在C中,我没有注意到函数声明之前使用的extern关键字的任何效果。 起初,我认为在定义extern int f()时在单个文件中强制您在文件范围之外实现它。然而,我发现这两个方面: extern int f(); int f() {return 0;} 及 编译很好,没有来自gcc的警告。我使用了gcc-Wall-ansi;它甚至不接受/注释 在函数定义之前使用extern是否有任何影响?或者它只是一个可选的关键字,对函数没有副作用 在后一种情况下,我不明白为什么标准设计者选择在语法中添加多

在C中,我没有注意到函数声明之前使用的
extern
关键字的任何效果。 起初,我认为在定义
extern int f()时在单个文件中强制您在文件范围之外实现它。然而,我发现这两个方面:

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

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

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

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


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

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

据我所知,默认情况下,所有函数声明都被视为“extern”,因此无需显式指定它

extern int f() {return 0;}

这并没有使这个关键字变得无用,因为它也可以与变量一起使用(在这种情况下,它是解决链接问题的唯一解决方案)。但是对于函数,是的,它是可选的。

我们有两个文件,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;
}
#包括
易失性无符号int stop\u now=0;
外部无效条函数(void);
内部主(空)
{
而(1){
bar_函数();
立即停止=1;
}
返回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);
   }
}
#包括
extern volatile unsigned int stop_now;
void bar_函数(void)
{
如果(!立即停止){
printf(“你好,世界!\n”);
睡眠(30);
}
}
正如您所看到的,我们在foo.c和bar.c之间没有共享头,但是bar.c在链接时需要在foo.c中声明的内容,而foo.c在链接时需要bar.c中的函数

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

如果您需要在模块之间共享一些全局数据,并且不想将其放入/初始化到头中,那么它非常有用

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

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

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标识符的链接

[……]

4对于使用存储类说明符extern声明的标识符,在该标识符的先前声明可见的范围内,23)如果先前声明指定了内部或 外部链接,后面声明中标识符的链接与 先前声明中指定的链接。如果之前的声明不可见,或者如果之前的 声明未指定任何链接,则标识符具有外部链接

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


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

如果我写

extern int i;
在C文件的文件范围(函数块之外)中,则表示“变量可能在其他地方定义”

是函数f的声明和函数f的定义。本例中的定义凌驾于外部

extern int f();
int f() {return 0;}
首先是声明,然后是定义

如果要声明并同时定义文件,则使用
extern
是错误的
extern int f();
int f() {return 0;}
extern int i = 4;
#include "a.h"

int i = 2;

int f() { i++; return i;}
extern int i;
int f(void);
#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}
  @@
    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
@@
type T;
identifier f;
@@
- extern
  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/
$ 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/
    int f() {return 0;}
    extern int f() {return 0;}