C中的static和extern有什么区别?

vlf7wbxs  于 2023-10-16  发布在  其他
关注(0)|答案(5)|浏览(93)

staticextern在C语言中有什么区别?

x7yiwoj4

x7yiwoj41#

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

static存储类用于声明一个标识符,该标识符是函数或文件的局部变量,并且在控制从声明的位置传递后存在并保留其值。此存储类具有永久的持续时间。声明为此类的变量从函数的一次调用到下一次调用都保留其值。范围是本地的。一个变量只被它在其中声明的函数所知道,或者如果在一个文件中全局声明,它只被该文件中的函数所知道或看到。这个存储类保证变量的声明也会将变量置零或关闭所有位。
extern存储类用于声明一个全局变量,该变量将为文件中的函数所知,并能够为程序中的所有函数所知。此存储类具有永久的持续时间。这个类的任何变量保持其值,直到被另一个赋值改变。范围是全球性的。一个变量可以被程序中的所有函数所知道或看到。

ulydmbyx

ulydmbyx2#

static意味着一个变量只在这个文件中全局已知。extern意味着在另一个文件中定义的全局变量在这个文件中也是已知的,并且也用于访问在其他文件中定义的函数。
函数中定义的局部变量也可以声明为static。这将导致与定义为全局变量相同的行为,但仅在函数内部可见。这意味着你得到了一个局部变量,它的存储是永久的,因此在调用该函数之间会保留它的值。
我不是CMaven,所以我可能错了,但这就是我如何理解staticextern的。希望有更有知识的人能给你一个更好的答案。

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

vqlkdk9b

vqlkdk9b3#

您可以将static应用于变量和函数。有两个答案讨论了staticextern关于变量的行为,但都没有真正涵盖函数。这是为了弥补这一缺陷。

TL;DR

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

外部函数

默认情况下,C中的函数在定义它们的翻译单元(TU -基本上是C源文件和包含的头文件)之外可见。这样的函数可以从任何代码中通过名称调用,通知编译器函数存在-通常通过头中的声明。
例如,标头<stdio.h>使printf()fprintf()scanf()fscanf()fopen()fclose()等函数的声明可见。如果源文件包含头文件,则可以调用函数。当程序被链接时,必须指定正确的库以满足函数定义。幸运的是,C编译器自动提供了标准C库中(大部分)函数的库(它通常提供比这些更多的函数)。“most of”警告适用,因为在许多系统(例如Linux,但不是macOS)上,如果您使用在<math.h>头中声明的函数,则需要链接到数学库(如果您是美国人,则为“math”库),这通常由链接器命令行上的选项-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编译器选项,例如:

$ 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声明。

通过名称调用vs通过指针调用

  • 如果你有神经质的倾向,现在就停止阅读。太惊险了!*

“按名称调用”注解意味着如果你有一个声明,比如:

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;简短的总结是“这不是一个好主意,除非可能与函数指针”。

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
$
s8vozzvw

s8vozzvw4#

这两个修饰符都与内存分配和代码链接有关。C标准[3]将它们称为存储类说明符。使用它们可以指定何时为对象分配内存和/或如何将其与代码的其余部分链接。让我们先来看看到底有什么需要说明的。

在C中链接

有三种类型的联系-外部,内部和没有。程序中的每个声明对象(即变量或函数)具有某种链接-通常由声明的环境指定。对象的链接说明了对象如何在整个程序中传播。链接可以通过关键字extern和static来修改。

外部链接

具有外部链接的对象可以在整个程序中跨模块查看(和访问)。默认情况下,在文件(或全局)范围内声明的任何内容都具有外部链接。默认情况下,所有全局变量和所有函数都具有外部链接。

内部链接

具有内部链接的变量和函数只能从一个编译单元访问-它们在其中定义的那个编译单元。具有内部链接的对象是单个模块的私有对象。

无链接

没有任何链接使对象对于它们被定义的范围是完全私有的。顾名思义,没有链接。这适用于所有局部变量和函数参数,它们只能从函数体内访问,而不能从其他地方访问。

存储时间

受这些关键词影响的另一个领域是存储时间,即。对象的生命周期贯穿程序运行时间。C中有两种类型的存储持续时间-静态和自动。
具有静态存储持续时间的对象在程序启动时初始化,并在整个运行时保持可用。所有具有外部和内部链接的对象也具有静态存储持续时间。对于没有链接的对象,默认为自动存储持续时间。这些对象在进入定义它们的块时被分配,并在块的执行结束时被删除。存储持续时间可以通过关键字static来修改。

静态

在C语言中,这个关键字有两种不同的用法。在第一种情况下,static修改变量或函数的链接。ANSI标准规定:
如果对象或函数的标识符声明具有文件作用域并包含存储类说明符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;
}

变量和函数()将具有内部链接,并且不会从任何其他模块可见。
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关键字和外部链接代表了两个不同的兴趣领域。外部链接是一个对象属性,表示可以从程序中的任何地方访问它。另一方面,关键字表示,声明的对象不是在这里定义的,而是在其他地方定义的。

h79rfbju

h79rfbju5#

Static使用关键字static声明的静态变量。静态变量的初始值为0。静态变量具有块文件作用域。
Extern一个C程序,特别是当它很大的时候,可以被分解成更小的程序。在编译这些之后,每个程序文件可以连接在一起以形成大程序。这些合并组合在一起的小程序模块可能需要一些它们都使用的变量。在C语言中,这样的规定可以通过指定这些变量来实现,所有小程序模块都可以访问这些变量,作为外部存储类变量。这些变量对于所有形成为单独文件的小程序模块是全局的。声明这种全局变量的关键字是extern。

这样一个全局变量在程序模块中的声明方式与任何其他变量一样,而在所有其他组合程序模块中,这些变量的声明都在关键字extern之前。
程序模块也可以是功能或块。只要程序在执行中,这些变量就一直存在,并且它们的存在不会在函数或块或程序模块从其执行状态退出时终止。这些变量存储在主存储器中,其默认值为零。Storage classes in C

相关问题