在C语言中,static和extern有什么区别?

58
在C语言中,staticextern有什么区别?
5个回答

47

来自http://wiki.answers.com/Q/What_is_the_difference_between_static_and_extern:

静态(static)存储类用于声明一个标识符,该标识符是函数或文件的本地变量,并且在控制从声明它的位置传递后仍然存在并保留其值。此存储类具有永久持续时间。此类别声明的变量在一次调用函数到下一次调用函数期间保留其值。范围为本地。只有声明它的函数或在文件中全局声明的变量才知道它。该存储类保证了变量的声明也将变量初始化为零或全部位关闭。

外部(extern)存储类用于声明全局变量,这些变量将为文件中的函数所知,并能够为程序中所有函数所知。此存储类具有永久持续时间。此类别的任何变量保留其值,直到通过另一个赋值更改为止。范围为全局。所有函数都可以了解或看到此类变量。


38

static 表示一个变量仅在此文件中全局可知。 extern 表示在另一个文件中定义的全局变量也可以在此文件中使用,同时还用于访问在其他文件中定义的函数。

在函数中定义的局部变量也可以声明为 static。这会导致与定义为全局变量相同的行为,但仅在函数内部可见。这意味着您获得了一个存储是永久性的局部变量,因此保留了其在调用该函数时的值。

我不是C语言专家,所以可能有错误,但这就是我理解 staticextern 的方式。希望更有经验的人能为您提供更好的答案。

编辑:根据JeremyP提供的评论进行了更正。


4
技术上来说,"extern"的意思是指在另一个文件中定义的全局变量也会在这个文件中得到识别。在C语言中,“定义”是指创建对象(并可选择初始化)的位置。除此之外,你的理解基本正确(将在块中限定为静态的变量忽略掉)。 - JeremyP
@JeremyP:啊,当然。我是指“定义”,但把这两个术语搞混了。感谢您指出这一点(+1);我已经相应地更新了答案,并添加了有关函数中静态变量的“事情”。^^ - gablin
静态作用域是在声明的位置内,例如如果它在函数内部声明,则其作用域仅限于该函数。我们可以说它是局部的。它的生命周期是永久的。外部作用域是全局的,生命周期也是永久的。有关更多信息,请访问以下链接:link - Durai Amuthan.H

18
您可以将 static 应用于变量和函数。有两个答案讨论了关于变量的 staticextern 的行为,但都没有涉及到函数。这是试图弥补这一缺陷的尝试。

简而言之

  • 尽可能使用静态函数。
  • 只在头文件中声明外部函数。
  • 在定义函数和使用函数的地方使用头文件。
  • 不要在其他函数内部声明函数。
  • 不要利用GCC扩展,在其他函数内嵌套定义函数。

外部函数

C语言中,默认情况下,函数在定义它们的翻译单元(TU-基本上是C源文件和包含的头文件)之外可见。这样的函数可以通过名称从任何通知编译器函数存在的代码中调用,通常是通过头文件中的声明。

例如,头文件<stdio.h>使得像printf()fprintf()scanf()fscanf()fopen()fclose()等函数的声明可见。如果一个源文件包括该头文件,则可以调用这些函数。当程序链接时,必须指定正确的库以满足函数定义。幸运的是,C编译器自动提供了提供(大部分)标准C库函数的库(通常提供的函数远不止这些)。需要注意的是,在头文件中声明外部函数。每个外部函数应该在一个头文件中声明,但一个头文件可以声明多个函数。该头文件应该同时用于定义每个函数的TU和使用该函数的每个TU。您不应该需要在源文件(而不是头文件)中编写全局函数的声明 - 应该有一个头文件来声明该函数,并且您应该使用该头文件来声明它。

静态函数

作为一种替代普遍可见函数的方法,您可以将自己的函数设置为static。这意味着不能从定义它的TU之外的代码中通过名称调用该函数。它是一个隐藏函数。

静态函数的主要优点是隐藏外部世界不需要知道的细节。这是一种基本但强大的信息隐藏技术。您还应该知道,如果一个函数是静态的,您不需要查找当前TU之外的函数使用情况,这可以极大地简化搜索过程。然而,如果函数是“静态”的,则可能会有多个TU,每个TU都包含具有相同名称的函数定义 - 每个TU都有自己的函数,这些函数可能与不同TU中具有相同名称的函数做相同或不同的事情。
在我的代码中,默认情况下将除了main()以外的所有函数都标记为关键字static - 除非有声明该函数的头文件。如果我随后需要从其他地方使用该函数,则可以将其添加到适当的头文件中,并从其定义中删除关键字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++所强制执行的。这也是我将大多数函数定义为静态函数并在使用前定义函数的另一个原因。另一种选择是在文件顶部声明静态函数,然后按照适当的顺序进行定义。这两种技术都有一些优点;我更喜欢通过在使用前进行定义来避免在文件中声明和定义相同的函数。
请注意,您不能在另一个函数内声明静态函数。如果您尝试将函数(例如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) { ... }。 然后,在另一个TU openness.c中,您不能写:
hidden_function(i);

只有定义了隐藏函数的TU才能直接使用它。然而,如果在reveal.c中有一个函数返回指向hidden_function()的函数指针,那么openness.c可以调用该函数(按名称)来获取指向隐藏函数的指针。

reveal1.h

extern void (*(revealer(void)))(int);

显然,这是一个不带参数并返回一个指向函数的指针的函数。这并不美观。在指向函数的指针上使用typedef的情况之一是(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;
}

示例输出

并不是世界上最令人兴奋的输出!


(Note: 保留了原文中的HTML标签)
$ openness1
openness1.c:main: 7
reveal1.c:hidden_function(): 37
$ openness2
openness2.c:main: 7
reveal2.c:hidden_function(): 37
$

15

这两个修饰符与内存分配和代码链接有关。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 关键字和外部链接代表着两个不同的关注点。外部链接是一个对象属性,表示它可以从程序中的任何地方访问。另一方面,该关键字表示所声明的对象在这里没有定义,而是在其他地方定义。


变量没有隐式的外部链接。它们有吗? - Sourav Kannantha B
目前为止最好的答案!虽然有点长,但非常深刻! - Girl Spider
1
只是为了澄清,外部链接意味着程序中的其他文件可以看到在.c文件中定义的全局变量。这并不意味着其他文件会自动看到它。您仍然需要在其他文件中使用关键字“extern”来引用此变量。 因此,在另一个文件中使用“extern int x;”就像是在说我要使用程序中某个地方定义的x。 - Girl Spider

0

静态变量 使用关键字static声明的静态变量。静态变量的初始值为0。静态变量具有块文件作用域。

外部变量 在C语言中,特别是当程序很大时,可以将其分解为较小的程序。编译这些程序后,每个程序文件都可以合并在一起形成大型程序。这些组合在一起的小程序模块可能需要一些变量,这些变量被所有模块使用。在C语言中,可以通过指定这些变量为外部存储类变量来实现这样的规定。这些变量对于形成单独文件的所有小程序模块都是全局的。声明此类全局变量的关键字是extern。

这样的全局变量像程序模块中的任何其他变量一样声明,而这些变量的声明在所有其他组合程序模块中都以关键字extern开头。

程序模块也可以是函数或块。这些变量在程序执行期间保持存在,并且它们的存在不会在函数、块或程序模块从其执行状态退出时终止。这些变量存储在主内存中,它们的默认值为零。 C语言中的存储类


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接