【发布时间】:2011-04-10 17:05:03
【问题描述】:
static 和 extern 在 C 中的区别是什么?
【问题讨论】:
标签: c
static 和 extern 在 C 中的区别是什么?
【问题讨论】:
标签: c
来自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 库中的(大部分)函数的库(而且它通常提供的函数远不止这些函数)。 “大部分”警告适用,因为在许多系统(例如 Linux,但不是 macOS)上,如果您使用在 <math.h> 标头中声明的函数,则需要链接到数学库(如果您是“数学”库) re American),通常由链接器命令行上的选项-lm 指示。
请注意,外部函数应在标头中声明。每个外部函数都应该在一个头文件中声明,但一个头文件可以声明多个函数。在定义每个函数的 TU 和使用该函数的每个 TU 中都应使用该标头。您永远不需要在源文件(而不是头文件)中为全局函数编写声明——应该有一个头文件来声明函数,并且您应该使用该头文件来声明它。
作为一般可见函数的替代方法,您可以创建自己的函数static。这意味着不能从定义它的 TU 外部按名称调用该函数。这是一个隐藏功能。
静态函数的主要优点是隐藏了外界不需要知道的细节。它是一种基本但功能强大的信息隐藏技术。你也知道,如果一个函数是静态的,你不需要在当前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 在函数内部的函数声明中不是必需的;如果省略,则假定为。这可能会在 SO 上的新手程序中导致意外行为 - 您有时会在打算调用的地方找到函数声明。
对于 GCC,选项 -Wnested-externs 标识嵌套的 extern 声明。
如果您有紧张的性格,请立即停止阅读。这让人毛骨悚然!
“按名称调用”注释意味着如果您有如下声明:
extern int function(void);
你可以写在你的代码中:
int i = function();
编译器和链接器会整理出一些东西,以便调用函数并使用结果。函数声明中的extern 是可选但显式的。我通常在头文件中使用它来匹配那些罕见的全局变量的声明——其中extern 不是可选的,而是强制性的。很多人不同意我的观点;随心所欲(或必须)。
那么静态函数呢?
假设 TU reveal.c 定义了一个函数 static void hidden_function(int) { … }。
然后,在另一个TUopenness.c,你不能写:
hidden_function(i);
只有定义了隐藏函数的TU才能直接使用。但是,如果 reveal.c 中有一个函数返回指向 hidden_function() 的函数指针,则代码 openness.c 可以调用该其他函数(按名称)以获取指向隐藏函数的指针。
reveal1.hextern void (*(revealer(void)))(int);
显然,这是一个不带参数的函数,并返回一个指向带有int 参数且不返回值的函数的指针。不;它不漂亮。在指针上使用typedef 有意义的时候之一是使用指向函数的指针 (reveal2.h):
typedef void (*HiddenFunctionType)(int);
extern HiddenFunctionType revealer(void);
那里:更容易理解。
有关typedef 主题的一般性讨论和指针,请参见Is it a good idea to typedef pointers;简短的总结是“这不是一个好主意,除非有函数指针”。
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 标准[3] 将它们称为存储类说明符。使用这些允许您指定何时为您的对象分配内存和/或如何将其与其余代码链接。让我们先看看究竟有什么要指定的。
C 中的链接
共有三种类型的链接——外部、内部和无。程序中的每个声明对象(即变量或函数)都有某种链接——通常由声明的环境指定。对象的链接表示对象如何在整个程序中传播。可以通过关键字 extern 和 static 来修改链接。
外联
通过模块的整个程序可以看到(和访问)具有外部链接的对象。默认情况下,您在文件(或全局)范围内声明的任何内容都具有外部链接。默认情况下,所有全局变量和所有函数都有外部链接。
内部链接
具有内部链接的变量和函数只能从一个编译单元访问,即定义它们的那个。具有内部链接的对象是单个模块的私有对象。
无链接
无链接使对象在定义它们的范围内完全私有。顾名思义,不进行链接。这适用于所有局部变量和函数参数,它们只能从函数体内访问,其他任何地方都无法访问。
存储时长
受这些关键字影响的另一个方面是存储持续时间,即对象在程序运行时的生命周期。 C 中有两种类型的存储持续时间 - 静态和自动。
具有静态存储持续时间的对象在程序启动时初始化,并在整个运行时保持可用。所有具有外部和内部链接的对象也具有静态存储持续时间。对于没有链接的对象,自动存储持续时间是默认设置。这些对象在进入定义它们的块时被分配,并在块的执行结束时被删除。存储时长可以通过关键字 static 修改。
静态
这个关键字在 C 语言中有两种不同的用法。在第一种情况下,静态修改变量或函数的链接。 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 进行修改,但关键字有多种用途。
extern 关键字和外部链接也代表了两个不同的领域。外部链接是一个对象属性,表示可以从程序中的任何位置访问它。另一方面,关键字表示声明的对象不是在这里定义的,而是在其他地方定义的。
【讨论】:
静态 使用关键字 static 声明的静态变量。静态变量初始值为0。静态变量具有块文件作用域。
外部 C 中的程序,特别是当它很大时,可以分解成更小的程序。编译这些之后,可以将每个程序文件连接在一起形成大程序。这些组合在一起的小程序模块可能需要一些可供所有人使用的变量。在 C 语言中,可以通过将所有小程序模块都可以访问的这些变量指定为外部存储类变量来进行这样的规定。这些变量对于形成为单独文件的所有小程序模块都是全局的。声明此类全局变量的关键字是 extern。
这种全局变量的声明与其中一个程序模块中的任何其他变量一样,而在所有其他组合程序模块中,这些变量的声明前面都带有关键字 extern。
程序模块也可以是函数或块。只要程序正在执行,这些变量就一直存在,并且它们的存在不会在函数或块或程序模块从其执行状态退出时终止。这些变量存储在主存储器中,它们的默认值为零。 Storage classes in C
【讨论】: