C中的静态和外部有什么区别?
C中的static
和extern
什么区别?
来自http://wiki.answers.com/Q/What_is_the_difference_between_static_and_extern :
静态存储类用于声明一个标识符,该标识符是函数或文件的局部变量,并且存在并在控制从声明它的位置传递之后保留其值。 此存储类的持续时间是永久性的。 声明此类的变量将从函数的一次调用到下一次调用保留其值。 范围是本地的。 变量只能通过声明的函数知道,或者如果在文件中全局声明,则只有该文件中的函数才知道或看到它。 此存储类保证变量的声明还将变量初始化为零或关闭所有位。
extern存储类用于声明一个全局变量,该变量将为文件中的函数所知,并且能够为程序中的所有函数所知。 此存储类的持续时间是永久性的。 此类的任何变量都会保留其值,直到另一个赋值更改为止。 范围是全球性的。 程序中的所有函数都可以知道或看到变量。
static
表示只在此文件中全局知道变量。 extern
表示在另一个文件中定义的全局变量也将在此文件中已知,并且还用于访问在其他文件中定义的函数。
函数中定义的局部变量也可以声明为static
。 这会导致相同的行为,就好像它被定义为全局变量一样,但只在函数内部可见。 这意味着您将获得一个本地变量,其存储是永久性的,因此在对该函数的调用之间保留其值。
我不是C专家,所以我可能错了,但这就是我对static
和extern
理解。 希望有更多知识渊博的人能够为您提供更好的答案。
编辑:根据JeremyP提供的评论更正答案。
您可以对变量和函数应用static
。 有两个答案讨论static
和extern
关于变量的行为,但两者都没有真正涵盖函数。 这是为了纠正这种不足。
TL; DR
- 尽可能使用静态function。
- 仅在标头中声明外部函数。
- 使用定义函数的标题以及使用函数的位置。
- 不要在其他函数中声明函数。
- 不要利用嵌套在其他函数中的函数定义来利用GCC扩展。
外部function
默认情况下,C中的函数在转换单元(TU – 基本上是C源文件和包含的标头)之外可见,它们在这些单元中定义。 这些函数可以通过名称从任何代码调用,通知编译器该函数存在 – 通常通过标头中的声明。
例如,头文件
可以显示函数的可见声明,例如printf()
, fprintf()
, scanf()
, fscanf()
, fopen()
, fclose()
等。 如果源文件包含标头,则可以调用这些函数。 链接程序时,必须指定正确的库以满足函数定义。 幸运的是,C编译器自动提供了提供(大部分)标准C库中的函数的库(它通常提供了比这些更多的函数)。 “大多数”警告适用,因为在许多系统(例如Linux,但不是macOS)上,如果使用
标头中声明的函数,则需要链接数学库(’math’库,如果你是美国人),通常由链接器命令行上的选项-lm
表示。
请注意,外部函数应在标头中声明。 每个外部函数应该在一个头中声明,但是一个头可以声明许多函数。 标题应该在定义了每个函数的TU和使用该函数的每个TU中使用。 您永远不需要在源文件中编写全局函数的声明(而不是头文件) – 应该有一个标头来声明该函数,您应该使用该标头来声明它。
静态function
作为通常可见function的替代方案,您可以将自己的function设置为static
。 这意味着无法通过名称从定义它的TU外部调用该函数。 这是一个隐藏的function。
静态函数的主要优点是隐藏外部世界不需要了解的细节。 它是一种基本但function强大的信息隐藏技术。 您还知道,如果函数是静态的,您不需要在当前TU之外查找函数的使用,这可以大大简化搜索。 但是,如果函数是static
,则可以有多个TU,每个TU包含具有相同名称的函数的定义 – 每个TU都有自己的函数,它可能与也可能不同于具有相同名称的函数。一个不同的TU。
在我的代码中,我默认使用关键字static
限定除main()
之外的所有函数 – 除非有一个声明函数的头。 如果我随后需要使用其他地方的函数,可以将其添加到相应的标头中,并从其定义中删除关键字static
。
在其他函数中声明函数
在另一个函数的范围内声明一个函数是可能的,但是非常不可取。 这些声明面对敏捷发展格言,如SPOT(单点真相)和DRY(不要重复自己)。 他们也是维护责任。
但是,如果您愿意,可以编写如下代码:
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 $
我发现,这是一个很好的学科,类似于C ++强制执行的。 这是我将大多数函数设置为静态的另一个原因,并在使用它们之前定义它们。 另一种方法是在文件顶部声明静态函数,然后以适当的顺序定义它们。 两种技术都有一些优点; 我更喜欢通过在使用之前定义来避免在文件中声明和定义相同的函数。
请注意,您不能在另一个函数中声明static
函数,并且如果您尝试将诸如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
; 如果省略,则假设。 这可能会导致新手程序出现意外行为 - 你有时会找到一个函数声明来进行调用。
使用GCC,选项-Wnested-externs
识别嵌套的extern
声明。
由名称vs调用指针调用
如果你有紧张的性格,现在就停止阅读。 这变得毛茸茸!
“按名称调用”注释意味着如果您有一个声明,例如:
extern int function(void);
你可以写下你的代码:
int i = function();
并且编译器和链接器将对事物进行排序,以便调用函数并使用结果。 函数声明中的extern
是可选的但是显式的。 我通常在头文件中使用它来匹配那些罕见的全局变量的声明 - 其中extern
不是可选的但是必需的。 许多人对此不以为然; 按你的意愿(或必须)做。
那么静态函数呢? 假设TU reveal.c
定义了一个函数static void hidden_function(int) { … }
。 然后,在另一个TU openness.c
,你不能写:
hidden_function(i);
只有定义隐藏function的TU才能直接使用它。 但是,如果在reveal.c
中有一个函数返回一个指向hidden_function()
的函数指针,那么代码hidden_function()
可以调用其他函数(按名称)来获取指向隐藏函数的指针。
reveal1.h
extern void (*(revealer(void)))(int);
显然,这是一个不带参数的函数,并返回一个指向函数的指针,该函数接受一个int
参数并且不返回任何值。 没有; 它不漂亮。 有一次在指针上使用typedef
是指向函数的指针( reveal2.h
):
typedef void (*HiddenFunctionType)(int); extern HiddenFunctionType revealer(void);
有:更容易理解。
请参阅typedef指针以获取有关typedef
和指针主题的一般性讨论; 这是一个好主意 ; 简短的总结是“除了函数指针之外,它不是一个好主意”。
reveal1.c
#include #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()
可以由hidden_function()
返回,可以通过reveal.c
的代码reveal.c
。 您可以在不更改程序含义的情况下删除extern
。
openness1.c
#include #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
声明的reveal.h
revealer()
函数可以通过名称调用,并返回一个指向隐藏函数的指针,然后可以使用该函数。
reveal2.c
#include #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 #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标准[3]将它们称为存储类说明符。 使用这些允许您指定何时为对象分配内存和/或如何将其与其余代码链接。 让我们看一下首先要指定的内容。
在C中链接
有三种类型的联系 – 外部,内部和无。 程序中的每个声明对象(即变量或函数)都有某种联系 – 通常由声明的环境指定。 对象的链接说明对象如何在整个程序中传播。 可以通过关键字extern和static修改链接。
外部联动
可以通过模块中的整个程序查看(和访问)具有外部链接的对象。 您在文件(或全局)范围声明的任何内容都默认具有外部链接。 默认情况下,所有全局变量和所有函数都具有外部链接
内部联系
具有内部链接的变量和函数只能从一个编译单元访问 – 它们是在其中定义的。具有内部链接的对象对于单个模块是私有的。
无链接
无链接使对象完全属于它们所定义的范围。顾名思义,不进行链接。 这适用于所有局部变量和函数参数,这些变量和函数参数只能在函数体内访问,而不能在其他任何地方访问。
储存期限
受这些关键字影响的另一个区域是存储持续时间,即通过程序运行时间对象的生命周期。 C中有两种类型的存储持续时间 – 静态和自动。
具有静态存储持续时间的对象在程序启动时初始化,并在整个运行时期间保持可用。 具有外部和内部链接的所有对象也具有静态存储持续时间。 对于没有链接的对象,默认自动存储持续时间。 这些对象在进入定义它们的块时被分配,并在块的执行结束时被移除。 存储持续时间可以通过关键字static修改。
静态的
C语言中此关键字有两种不同的用法。 在第一种情况下,static修改变量或函数的链接。 ANSI标准规定:
如果对象或函数的标识符声明具有文件范围并且包含存储类说明符static,则标识符具有内部链接。
这意味着如果在文件级别(即不在函数中)使用static关键字,它将把对象的链接更改为internal,使其仅对文件或更准确地说是编译单元是私有的。
/* 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中静态关键字的另一个用途是指定存储持续时间。 该关键字可用于将自动存储持续时间更改为静态。 函数内部的静态变量只分配一次(在程序启动时),因此它在调用之间保持其值
#include 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关键字表示“此标识符在此处声明,但在其他地方定义”。 换句话说,您告诉编译器某些变量可用,但其内存在其他地方分配。 事情是,在哪里? 让我们先看看声明和某些对象定义之间的区别。 通过声明一个变量,您可以说明变量的类型以及稍后在程序中的名称。 例如,您可以执行以下操作:
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; }
这将编译没有错误。
摘要
请记住,静态 - 存储类说明符和静态存储持续时间是两回事。 存储持续时间是对象的属性,在某些情况下可以通过静态修改,但关键字具有多种用途。
此外,extern关键字和外部链接代表两个不同的感兴趣领域。 外部链接是一个对象属性,表示可以从程序中的任何位置访问它。 另一方面,关键字表示声明的对象不是在这里定义的,而是在其他地方定义的。