static
和extern
有什么区别?来自http://wiki.answers.com/Q/What_is_the_difference_between_static_and_extern:
静态(static)存储类用于声明一个标识符,该标识符是函数或文件的本地变量,并且在控制从声明它的位置传递后仍然存在并保留其值。此存储类具有永久持续时间。此类别声明的变量在一次调用函数到下一次调用函数期间保留其值。范围为本地。只有声明它的函数或在文件中全局声明的变量才知道它。该存储类保证了变量的声明也将变量初始化为零或全部位关闭。
外部(extern)存储类用于声明全局变量,这些变量将为文件中的函数所知,并能够为程序中所有函数所知。此存储类具有永久持续时间。此类别的任何变量保留其值,直到通过另一个赋值更改为止。范围为全局。所有函数都可以了解或看到此类变量。
static
表示一个变量仅在此文件中全局可知。 extern
表示在另一个文件中定义的全局变量也可以在此文件中使用,同时还用于访问在其他文件中定义的函数。
在函数中定义的局部变量也可以声明为 static
。这会导致与定义为全局变量相同的行为,但仅在函数内部可见。这意味着您获得了一个存储是永久性的局部变量,因此保留了其在调用该函数时的值。
我不是C语言专家,所以可能有错误,但这就是我理解 static
和 extern
的方式。希望更有经验的人能为您提供更好的答案。
编辑:根据JeremyP提供的评论进行了更正。
static
应用于变量和函数。有两个答案讨论了关于变量的 static
和 extern
的行为,但都没有涉及到函数。这是试图弥补这一缺陷的尝试。
C语言中,默认情况下,函数在定义它们的翻译单元(TU-基本上是C源文件和包含的头文件)之外可见。这样的函数可以通过名称从任何通知编译器函数存在的代码中调用,通常是通过头文件中的声明。
例如,头文件<stdio.h>
使得像printf()
、fprintf()
、scanf()
、fscanf()
、fopen()
、fclose()
等函数的声明可见。如果一个源文件包括该头文件,则可以调用这些函数。当程序链接时,必须指定正确的库以满足函数定义。幸运的是,C编译器自动提供了提供(大部分)标准C库函数的库(通常提供的函数远不止这些)。需要注意的是,在头文件中声明外部函数。每个外部函数应该在一个头文件中声明,但一个头文件可以声明多个函数。该头文件应该同时用于定义每个函数的TU和使用该函数的每个TU。您不应该需要在源文件(而不是头文件)中编写全局函数的声明 - 应该有一个头文件来声明该函数,并且您应该使用该头文件来声明它。
作为一种替代普遍可见函数的方法,您可以将自己的函数设置为static
。这意味着不能从定义它的TU之外的代码中通过名称调用该函数。它是一个隐藏函数。
main()
以外的所有函数都标记为关键字static
- 除非有声明该函数的头文件。如果我随后需要从其他地方使用该函数,则可以将其添加到适当的头文件中,并从其定义中删除关键字static
。extern int processor(int x);
int processor(int x)
{
extern int subprocess(int);
int sum = 0;
for (int i = 0; i < x; i++)
sum += subprocess((x + 3) % 7);
return sum;
}
extern int subprocess(int y);
int subprocess(int y)
{
return (y * 13) % 37;
}
processor()
函数中的声明足以让其使用subprocess()
,但是除此之外不够完美。如果您使用GCC编译器选项,例如:
extern
声明在定义之前是必要的。
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
> -c process.c
process.c:12:5: error: no previous prototype for ‘subprocess’ [-Werror=missing-prototypes]
int subprocess(int y)
^~~~~~~~~~
cc1: all warnings being treated as errors
$
subprocess()
)定义为静态函数,则编译器会报错。process.c:12:16: error: static declaration of ‘subprocess’ follows non-static declaration
static int subprocess(int y)
^~~~~~~~~~
process.c:5:20: note: previous declaration of ‘subprocess’ was here
extern int subprocess(int);
^~~~~~~~~~
由于外部可见的函数应该在头文件中声明,所以没有必要在函数内部声明它们,因此您不应该遇到这样的问题。
同样,在函数内部的函数声明中,extern
是不必要的;如果省略,则会被假定。这可能会导致初学者在SO上出现意外行为——有时会发现函数声明,而实际想要的是调用。
使用GCC,选项-Wnested-externs
可以识别嵌套的extern
声明。
如果您紧张,现在停止阅读。这很棘手!
“按名称调用”意味着如果您有这样的声明:
extern int function(void);
你可以在你的代码中写入:
int i = function();
extern
是可选的但显式的。我通常在头文件中使用它来匹配那些罕见的全局变量的声明 - 在这种情况下extern
不是可选的而是强制的。许多人对此持不同意见;按照您的意愿(或必须)进行操作。reveal.c
定义了一个函数static void hidden_function(int) { ... }
。
然后,在另一个TU openness.c
中,您不能写:hidden_function(i);
只有定义了隐藏函数的TU才能直接使用它。然而,如果在reveal.c
中有一个函数返回指向hidden_function()
的函数指针,那么openness.c
可以调用该函数(按名称)来获取指向隐藏函数的指针。
reveal1.h
extern void (*(revealer(void)))(int);
reveal2.h
):typedef void (*HiddenFunctionType)(int);
extern HiddenFunctionType revealer(void);
这样理解起来简单得多。
参见typedef指针是否是一个好主意,其中对于typedef
和指针的讨论很普遍,简短的总结是“除了函数指针,它并不是一个好主意”。
reveal1.c
#include <stdio.h>
#include "reveal1.h"
static void hidden_function(int x)
{
printf("%s:%s(): %d\n", __FILE__, __func__, x);
}
extern void (*(revealer(void)))(int)
{
return hidden_function;
}
是的,使用显式的extern
定义函数是合法的(但非常不寻常)——我很少这样做,但它强调了extern
的作用,并将其与static
进行对比。 hidden_function()
可以被revealer()
返回,并且可以通过reveal.c
中的代码调用。您可以删除extern
而不更改程序的含义。
openness1.c
#include <stdio.h>
#include "reveal1.h"
int main(void)
{
void (*revelation)(int) = revealer();
printf("%s:%s: %d\n", __FILE__, __func__, __LINE__);
(*revelation)(37);
return 0;
}
这个文件不能直接通过名称调用hidden_function()
,因为它在其他TU中是隐藏的。然而,在reveal.h
中声明的revealer()
函数可以通过名称调用,并返回一个指向隐藏函数的指针,然后可以使用它。
reveal2.c
#include <stdio.h>
#include "reveal2.h"
static void hidden_function(int x)
{
printf("%s:%s(): %d\n", __FILE__, __func__, x);
}
extern HiddenFunctionType revealer(void)
{
return hidden_function;
}
openness2.c
#include <stdio.h>
#include "reveal2.h"
int main(void)
{
HiddenFunctionType revelation = revealer();
printf("%s:%s: %d\n", __FILE__, __func__, __LINE__);
(*revelation)(37);
return 0;
}
并不是世界上最令人兴奋的输出!
$ openness1
openness1.c:main: 7
reveal1.c:hidden_function(): 37
$ openness2
openness2.c:main: 7
reveal2.c:hidden_function(): 37
$
这两个修饰符与内存分配和代码链接有关。C标准将它们称为存储类别说明符。使用它们可以指定何时为对象分配内存,以及如何将其与代码的其余部分链接。首先看一下要指定的具体内容。
C中的链接
有三种链接类型 - 外部、内部和无。程序中声明的每个对象(即变量或函数)都具有某种链接 - 通常由声明的情况指定。对象的链接表示该对象在整个程序中如何传播。链接可以通过关键字 extern 和 static 进行修改。
外部链接
具有外部链接的对象可以通过模块间全局访问(并引用)。默认情况下,在文件(或全局)范围内声明的任何内容都具有外部链接。所有全局变量和所有函数都具有外部链接。
内部链接
具有内部链接的变量和函数只能从一个编译单元(定义它们的单元)中访问。具有内部链接的对象对单个模块是私有的。
无链接
没有链接使对象完全限于它们被定义的范围内。顾名思义,没有进行链接。这适用于所有仅能从函数体内访问的局部变量和函数参数。
存储期
这些关键字影响的另一个领域是存储期,即对象在程序运行期间的生存时间。C中有两种类型的存储期 - 静态和自动。
具有静态存储期的对象在程序启动时初始化,并在整个运行时保持可用。所有具有外部和内部链接的对象也具有静态存储期。没有链接的对象默认具有自动存储期。这些对象在进入它们定义的块时分配,并在块的执行结束时移除。存储期可以通过关键字 static 进行修改。
静态
C语言中有两种不同的用法。在第一种情况下,static 修改变量或函数的链接。ANSI标准说明:
如果对象或函数的标识符声明具有文件作用域,并且包含存储类别说明符 static,则该标识符具有内部链接。
这意味着如果在文件级别(即不在函数中)使用 static 关键字,它将更改对象的链接到内部,使其仅对文件私有,或者更精确地说,编译单元私有。
/* This is file scope */
int one; /* External linkage. */
static int two; /* Internal linkage. */
/* External linkage. */
int f_one()
{
return one;
}
/* Internal linkage. */
static void f_two()
{
two = 2;
}
int main(void)
{
int three = 0; /* No linkage. */
one = 1;
f_two();
three = f_one() + two;
return 0;
}
变量和function()将具有内部链接,不会在任何其他模块中可见。
C语言中static关键字的另一种用法是指定存储期限。该关键字可用于将自动存储期限更改为静态存储期限。函数内的静态变量仅分配一次(在程序启动时),因此它在调用之间保持其值。
#include <stdio.h>
void foo()
{
int a = 10;
static int sa = 10;
a += 5;
sa += 5;
printf("a = %d, sa = %d\n", a, sa);
}
int main()
{
int i;
for (i = 0; i < 10; ++i)
foo();
}
输出结果将类似于这样:
a = 15, sa = 15
a = 15, sa = 20
a = 15, sa = 25
a = 15, sa = 30
a = 15, sa = 35
a = 15, sa = 40
a = 15, sa = 45
a = 15, sa = 50
a = 15, sa = 55
a = 15, sa = 60
外部变量
extern关键字表示“此标识符在此处声明,但在其他地方定义”。换句话说,您告诉编译器某些变量将可用,但其内存分配在其他地方。问题是,在哪里分配?首先让我们看一下声明和定义某个对象的区别。通过声明变量,您说明变量的类型以及稍后在程序中使用的名称。例如,您可以执行以下操作:
extern int i; /* Declaration. */
extern int i; /* Another declaration. */
变量在定义它之前(即分配内存之前)实际上是不存在的。 变量的定义如下所示: 变量在定义它之前(即分配内存之前)实际上是不存在的。 变量的定义如下所示:
int i = 0; /* Definition. */
你可以在程序中放置任意数量的声明,但是在一个作用域内只能有一个定义。以下是一个来自C标准的示例:
/* definition, external linkage */
int i1 = 1;
/* definition, internal linkage */
static int i2 = 2;
/* tentative definition, external linkage */
int i3;
/* valid tentative definition, refers to previous */
int i1;
/* valid tenative definition, refers to previous */
static int i2;
/* valid tentative definition, refers to previous */
int i3 = 3;
/* refers to previous, whose linkage is external */
extern int i1;
/* refers to previous, whose linkage is internal */
extern int i2;
/* refers to previous, whose linkage is external */
extern int i4;
int main(void) { return 0; }
这将会编译通过,没有错误。
概要
请记住,static——存储类说明符和静态存储期是两个不同的概念。存储期是对象的一个属性,在某些情况下可以被 static 修改,但该关键字有多个用途。
此外,extern 关键字和外部链接代表着两个不同的关注点。外部链接是一个对象属性,表示它可以从程序中的任何地方访问。另一方面,该关键字表示所声明的对象在这里没有定义,而是在其他地方定义。
静态变量 使用关键字static声明的静态变量。静态变量的初始值为0。静态变量具有块文件作用域。
外部变量 在C语言中,特别是当程序很大时,可以将其分解为较小的程序。编译这些程序后,每个程序文件都可以合并在一起形成大型程序。这些组合在一起的小程序模块可能需要一些变量,这些变量被所有模块使用。在C语言中,可以通过指定这些变量为外部存储类变量来实现这样的规定。这些变量对于形成单独文件的所有小程序模块都是全局的。声明此类全局变量的关键字是extern。
这样的全局变量像程序模块中的任何其他变量一样声明,而这些变量的声明在所有其他组合程序模块中都以关键字extern开头。
程序模块也可以是函数或块。这些变量在程序执行期间保持存在,并且它们的存在不会在函数、块或程序模块从其执行状态退出时终止。这些变量存储在主内存中,它们的默认值为零。 C语言中的存储类