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

Sam*_*ain 33 c

C staticexternC 之间有什么区别?

Dan*_*ail 32

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

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

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


gab*_*lin 24

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

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

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

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

  • 从技术上讲,你的意思是"extern意味着在另一个文件中定义的全局变量*也将在此文件中被识别".在C中,*definition*是"创建"对象(并可选择初始化)的位置.除此之外,你在钉子上(在地毯下的块中清扫变量合格). (4认同)

Jon*_*ler 9

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

TL; DR

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

外部功能

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

例如,报头<stdio.h>使得功能,诸如可见声明printf(),fprintf(),scanf(),fscanf(),fopen(),fclose(),等.如果源文件包含标头,则可以调用这些函数.链接程序时,必须指定正确的库以满足函数定义.幸运的是,C编译器自动提供了提供(大部分)标准C库中的函数的库(它通常提供了比这些更多的函数)."大多数"警告适用于许多系统(例如Linux,但不是macOS),如果使用<math.h>标题中声明的函数,则需要链接数学库(如果您是美国人,则为"数学"库) ,通常由-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;
}
Run Code Online (Sandbox Code Playgroud)

该声明processor()足以使用subprocess(),但在其他方面不令人满意.的extern,如果你使用GCC编译器选项,如定义之前的声明是必要的:

$ 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
$
Run Code Online (Sandbox Code Playgroud)

我发现,这是一个很好的学科,类似于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);
                    ^~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

由于外部可见的函数应该在头文件中声明,因此不需要在函数内声明它们,因此您不应该将此作为一个问题.

同样,extern在函数内部的函数声明中没有必要; 如果省略,则假设.这可能会导致新手程序出现意外行为 - 你有时会找到一个函数声明来调用.

使用GCC,该选项可-Wnested-externs识别嵌套extern声明.

由名称vs调用指针调用

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

"按名称调用"注释意味着如果您有一个声明,例如:

extern int function(void);
Run Code Online (Sandbox Code Playgroud)

你可以写下你的代码:

int i = function();
Run Code Online (Sandbox Code Playgroud)

并且编译器和链接器将对事物进行排序,以便调用函数并使用结果.将extern在函数的声明是可选的,但明确的.我通常在头文件中使用它来匹配那些罕见的全局变量的声明 - 其中extern不是可选的但是必需的.许多人对此不以为然; 按你的意愿(或必须)做.

那么静态函数呢?假设TU reveal.c定义了一个函数static void hidden_function(int) { … }.然后,在另一个TU openness.c,你不能写:

hidden_function(i);
Run Code Online (Sandbox Code Playgroud)

只有定义隐藏功能的TU才能直接使用它.但是,如果有一个函数reveal.c返回一个函数指针hidden_function(),那么代码openness.c可以调用其他函数(按名称)来获取指向隐藏函数的指针.

reveal1.h

extern void (*(revealer(void)))(int);
Run Code Online (Sandbox Code Playgroud)

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

typedef void (*HiddenFunctionType)(int);
extern HiddenFunctionType revealer(void);
Run Code Online (Sandbox Code Playgroud)

有:更容易理解.

请参阅输入def指针以进行关于主题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;
}
Run Code Online (Sandbox Code Playgroud)

是的,以明确的方式定义函数是合法的(但非常不寻常)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;
}
Run Code Online (Sandbox Code Playgroud)

此文件无法按名称包含直接调用,hidden_function()因为它隐藏在其他TU中.但是,revealer()声明的函数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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

样本输出

不是世界上最激动人心的产品!

$ openness1
openness1.c:main: 7
reveal1.c:hidden_function(): 37
$ openness2
openness2.c:main: 7
reveal2.c:hidden_function(): 37
$
Run Code Online (Sandbox Code Playgroud)


Raj*_*jat 8

这两个修饰符都与内存分配和代码链接有关。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;
}
Run Code Online (Sandbox Code Playgroud)

变量和 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();
}
Run Code Online (Sandbox Code Playgroud)

输出将如下所示:

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
Run Code Online (Sandbox Code Playgroud)

外部

extern 关键字表示“此标识符在此处声明,但在别处定义”。换句话说,您告诉编译器某个变量将可用,但它的内存分配在其他地方。问题是,在哪里?让我们先看看一些对象的声明和定义之间的区别。通过声明一个变量,您可以说明该变量的类型以及稍后在程序中使用的名称。例如,您可以执行以下操作:

extern int i; /* Declaration. */
extern int i; /* Another declaration. */
Run Code Online (Sandbox Code Playgroud)

该变量实际上不存在,直到您定义它(即为其分配内存)。变量的定义如下所示:

int i = 0; /* Definition. */
Run Code Online (Sandbox Code Playgroud)

你可以在你的程序中加入任意多的声明,但在一个范围内只能有一个定义。下面是一个来自 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; }
Run Code Online (Sandbox Code Playgroud)

这将编译没有错误。

概括

请记住,静态——存储类说明符和静态存储持续时间是两个不同的东西。Storage duration 是对象的一个​​属性,在某些情况下可以通过 static 进行修改,但该关键字有多种用途。

此外,extern 关键字和外部链接代表了两个不同的领域。外部链接是一个对象属性,表示可以从程序的任何地方访问它。另一方面,关键字表示声明的对象不是在这里定义的,而是在其他地方定义的。