C中的静态和外部有什么区别?

时间:2010-09-10 12:06:34

标签: c

C中的staticextern有什么区别?

5 个答案:

答案 0 :(得分:30)

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

  

static 存储类用于声明一个标识符,该标识符是函数或文件的局部变量,并且在控制从声明的位置传递后保留其值。此存储类的持续时间是永久性的。声明此类的变量将保留其从函数的一次调用到下一次的值。范围是本地的。变量只能通过声明的函数知道,或者如果在文件中全局声明,则只有该文件中的函数才知道或看到它。此存储类保证变量的声明也将变量初始化为零或关闭所有位。

     

extern 存储类用于声明一个全局变量,该变量将为文件中的函数所知,并且能够为程序中的所有函数所知。此存储类的持续时间是永久性的。此类的任何变量都保留其值,直到另一个赋值更改为止。范围是全球性的。程序中的所有函数都可以知道或看到变量。

答案 1 :(得分:22)

static表示变量仅在此文件中全局知晓。 extern表示在另一个文件中定义的全局变量也将在此文件中被识别,并且还用于访问在其他文件中定义的函数。

函数中定义的局部变量也可以声明为static。这会导致相同的行为,就好像它被定义为全局变量一样,但只在函数内部可见。这意味着您将获得一个本地变量,其存储是永久性的,因此在对该函数的调用之间保留其值。

我不是C专家所以我可能错了,但这就是我理解staticextern的方式。希望有更多知识渊博的人能够为您提供更好的答案。

编辑:根据JeremyP提供的评论更正答案。

答案 2 :(得分:9)

您可以将static应用于变量和函数。有两个答案讨论staticextern关于变量的行为,但两者都没有真正涵盖函数。这是为了纠正这种不足。

TL; DR

  • 尽可能使用静态功能。
  • 仅在标题中声明外部函数。
  • 使用定义函数的标题以及使用函数的位置。
  • 不要在其他功能中声明功能。
  • 不要使用嵌套在其他函数中的函数定义来利用GCC扩展。

外部功能

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

例如,标题<stdio.h>printf()fprintf()scanf()fscanf()fopen()等函数进行可见声明, fclose(),等等。如果源文件包含标头,则可以调用这些函数。链接程序时,必须指定正确的库以满足函数定义。幸运的是,C编译器自动提供了提供(大部分)标准C库中的函数的库(它通常提供了比这些更多的函数)。 &#39;大多数&#39;警告适用,因为在许多系统(例如Linux,但不是macOS)上,如果使用<math.h>标头中声明的函数,则需要链接数学库(&#39; math&#39;库,如果您是美国人),通常由链接器命令行上的选项-lm表示。

请注意,外部函数应在标头中声明。每个外部函数应该在一个头中声明,但是一个头可以声明许多函数。标题应该在定义了每个函数的TU和使用该函数的每个TU中使用。您永远不需要在源文件中编写全局函数的声明(而不是头文件) - 应该有一个标头来声明该函数,您应该使用该标头来声明它。

静态函数

作为常见功能的替代方法,您可以创建自己的函数static。这意味着无法通过名称从定义它的TU外部调用该函数。这是一个隐藏的功能。

静态功能的主要优点是隐藏了外界不需要了解的细节。它是一种基本但功能强大的信息隐藏技术。您还知道,如果函数是静态的,您不需要在当前TU之外查找函数的使用,这可以大大简化搜索。但是,如果函数是static,则可以有多个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 ++强制执行的类似。这是我将大部分功能设置为静态的另一个原因,并在它们被重新使用之前定义它们。另一种方法是在文件顶部声明静态函数,然后以适当的顺序定义它们。两种技术都有一些优点;我更喜欢通过在使用之前定义来避免在文件中声明和定义相同的函数。

请注意,您不能在另一个函数中声明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调用指针

调用

如果您有紧张的性格,请立即停止阅读。这变得毛茸茸!

名字叫&#39;注释意味着如果您有声明,例如:

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);

显然,这是一个不带参数的函数,它返回一个指向函数的指针,该函数接受int参数并且不返回任何值。没有;它不漂亮。有一次在指针上使用typedef是指向函数的指针(reveal2.h):

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

有:更容易理解。

有关typedef和指针主题的一般性讨论,请参阅Is it a good idea to typedef pointers;简短的总结是&#34;除了函数指针和#34;它不是一个好主意。

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的作用并将其与statichidden_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()

此文件无法通过名称直接调用revealer(),因为它隐藏在其他TU中。但是,reveal.h中声明的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
$

样本输出

不是世界上最令人兴奋的产品!

{{1}}

答案 3 :(得分:3)

这两个修饰符都与内存分配和代码链接有关。 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 <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关键字表示“此标识符在此处声明,但在其他地方定义”。换句话说,您告诉编译器某些变量可用,但其内存在其他地方分配。事情是,在哪里?让我们先看看声明和某些对象定义之间的区别。通过声明一个变量,您可以说明变量的类型以及稍后在程序中的名称。例如,您可以执行以下操作:

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关键字和外部链接也代表两个不同的兴趣领域。外部链接是一个对象属性,表示可以从程序中的任何位置访问它。另一方面,关键字表示声明的对象未在此处定义,而是在其他位置定义。

答案 4 :(得分:0)

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

外部 C语言中的程序(尤其是大型程序)可以分解成较小的程序。编译完这些文件后,可以将每个程序文件合并在一起以形成大型程序。这些组合在一起的小程序模块可能需要所有它们都使用的一些变量。在C语言中,可以通过指定所有小型程序模块都可以访问的这些变量作为外部存储类变量来进行这种设置。这些变量是作为单独文件形成的所有小程序模块的全局变量。声明此类全局变量的关键字是extern。

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

程序模块也可以是功能或块。只要程序正在执行,这些变量就一直存在,并且在函数或块或程序模块从其执行状态退出时,它们的存在不会终止。这些变量存储在主存储器中,其默认值为零。 Storage classes in C