在C语言中,“static”是什么意思?

1396

我在C语言代码中看到了static这个词,它是否像C#中的静态函数/类(其实现被多个对象共用)?


9
相关:Static(关键字)@ 维基百科静态关键字通常用于编程中,表示变量或函数是静态的,即它们在程序生命周期内仅被初始化一次并保持其值直到程序结束。静态变量存储在数据段中而不是堆栈中,因此对于需要跨多次函数调用保持相同值的变量非常有用。静态函数只能从与其定义在同一文件中的函数访问,无法从其他文件中的函数访问。 - Palec
42
为什么要从标题末尾删除“在C程序中”的内容,@Lundin?由于有 [tag:c] 标签存在,它稍微有些冗余。但是如果没有查看标签的情况下,将让我更快地看到分类。当我从可能包含其他语言问题的方向(比如 [tag:static] 或者 Google 搜索)进入问题时,这种冗余非常有用。 - Palec
7
我更喜欢在标题中保留“C”,因为SO只向标题附加一个标签(最常见的?)。如果有一天“语法”比C更多地出现在问题中(因为它是跨语言的东西),该怎么办?我宁愿使用明确的行为 :-) 编辑:但是有一个元问题说反了:https://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles - Ciro Santilli OurBigBook.com
3
这是我在Quora上找到的一个解释。绝对值得一读! - nalzok
1
静态变量的存储期限是程序结束,而不是作用域结束。 - user12211554
显示剩余2条评论
21个回答

1849
  1. 函数内的静态变量在多次调用之间保持其值不变。
  2. 静态全局变量或函数仅在声明它们的文件中可见。

如果你是初学者,那么(1)可能是更陌生的话题,下面是一个例子:

#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

在某些情况下,当一个函数需要在调用之间保留一些状态,并且你不想使用全局变量时,这个方法非常有用。但是要注意,这个特性应该非常少被使用 - 它会使你的代码不具备线程安全性并且更难理解。

(2) "静态"常常被用作"访问控制"功能。如果你有一个.c文件实现一些功能,通常只向用户公开几个“公共”的函数。其余的函数应该被设置为 static ,以便用户无法访问它们。这就是封装,一种好的实践方法。

引用自 Wikipedia:

在C编程语言中,“static”用于全局变量和函数,将它们的作用域设置为所在的文件。在局部变量中,“static”用于将变量存储在静态分配的内存中,而不是自动分配的内存中。虽然语言没有规定任何一种类型的内存实现方式,但是在编译时,静态分配的内存通常保留在程序的数据段中,而自动分配的内存通常作为瞬态调用栈来实现。

回答你的第二个问题,它与C#不同。

然而,在C++中,“static”也用于定义类属性(在所有相同类的对象之间共享)和方法。在C中没有类,因此这个特性是无关紧要的。


188
Pax,OP不知道静态,所以你建议让他陷入编译单元和文件之间的差异吗? :-) - Eli Bendersky
150
编译单元是编译器所见的单个文件。您的 .c 文件可能包括其他 .c 文件,但在预处理器处理包含文件后,编译器最终只看到一个“编译单元”。 - Eli Bendersky
91
编译器甚至不知道.h文件的存在 - 这些文件在预处理阶段被合并到.c文件中。因此,你可以说,将所有头文件包含在内的.c文件是一个单独的编译单元。 - Eli Bendersky
11
@TonyD,也许有些令人困惑,但这就是编译的工作原理。通常会有一个.c文件和一堆头文件,但魔鬼常常藏在不典型的地方。 - peterph
11
编译器负责编译。预处理器负责预处理。把工具链称为“编译器”并不会改变它的本质或功能。 - Miles Rout
显示剩余25条评论

288

这里还有一种用法未被涵盖,即作为数组类型声明的一部分,作为函数参数:

int someFunction(char arg[static 10])
{
    ...
}

在这个上下文中,这指定传递给此函数的参数必须是至少包含10个元素的char类型数组。有关更多信息,请参见我在这里的问题。


5
我不认为 C 有数组参数?Linus Torvalds 愤怒地抱怨人们这样做。 - suprjami
22
C语言没有数组参数,但是这种特定语法表示该函数期望从arg[0]arg[9]的数组元素有值(这也意味着该函数不接受空指针)。编译器可以利用这些信息进行优化,静态分析工具可以利用这些信息确保函数永远不会被传入空指针(或者如果能判断,数组元素小于指定值)。 - dreamlax
26
@Qix -- 这是C99中赋予static新的多重含义。它已经有十五年以上的历史了,但并不是所有编译器都支持C99的所有特性,因此C99作为一个整体仍然很少被人所熟知。 - Happy Green Kid Naps
1
@suprjami,我不完全确定你所说的“数组参数”的意思,但如果你指的是int arr[n];,那么这就是一个C99可变长度数组(VLA)。你是指这个吗? - RastaJedi
这是否意味着我不能将任何char*传递给此函数,因为没有人知道它是否会增加10...虽然你的答案很有趣,但我还是有疑问。 - Nikolai Ehrhardt

204

简短回答是:要看情况而定。

  1. 静态定义的本地变量在函数调用之间不会丢失其值。换句话说,它们是全局变量,但作用域仅限于它们所定义的本地函数。

  2. 静态全局变量在定义它们的C文件之外不可见。

  3. 静态函数在定义它们的C文件之外不可见。


10
“static function” 和 “private function” 的意思相同吗?同样,“static global variables” 和 “private global variables” 也是一个意思吗? - user1599964
47
这是关于C语言的内容。在C语言中没有私有/公有的概念。 - chris
32
尽管C语言中没有“private”关键字,但你的比喻很好:静态变量可以使得某些东西在给定文件中具有“私有”的属性。而在C语言中,文件通常对应于C++中的类。 - Ciro Santilli OurBigBook.com

85

多文件变量作用域示例

这里我将演示静态变量如何影响跨多个文件的函数定义作用域。

a.c

#include <stdio.h>

/*
Undefined behavior: already defined in main.
Binutils 2.24 gives an error and refuses to link.
https://dev59.com/CYbca4cB1Zd3GeqPVWhF
*/
/*int i = 0;*/

/* Works in GCC as an extension: https://dev59.com/oVDTa4cB1Zd3GeqPI2iJ#3692486 */
/*int i;*/

/* OK: extern. Will use the one in main. */
extern int i;

/* OK: only visible to this file. */
static int si = 0;

void a() {
    i++;
    si++;
    puts("a()");
    printf("i = %d\n", i);
    printf("si = %d\n", si);
    puts("");
}

主函数.c

#include <stdio.h>

int i = 0;
static int si = 0;

void a();    

void m() {
    i++;
    si++;
    puts("m()");
    printf("i = %d\n", i);
    printf("si = %d\n", si);
    puts("");
}

int main() {
    m();
    m();
    a();
    a();
    return 0;
}

GitHub upstream

编译并运行:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o

输出:

m()
i = 1
si = 1

m()
i = 2
si = 2

a()
i = 3
si = 1

a()
i = 4
si = 2

解释

  • si 有两个独立的变量,分别属于每个文件。
  • i 有一个共享变量。

通常情况下,作用域越小越好,所以如果可以,总是将变量声明为 static

C 编程中,文件通常用于表示“类”,而 static 变量表示类的私有静态成员。

相关标准规定

C99 N1256 草案 6.7.1 “存储类说明符” 规定了 static 是“存储类说明符”。

6.2.2/3 “标识符的链接属性” 表明 static 意味着“内部链接”:

如果对象或函数的文件范围标识符声明中包含存储类说明符 static,则该标识符具有内部链接。

6.2.2/2 表示,“内部链接” 的行为类似于我们的示例:

在组成整个程序的翻译单元和库集合中,具有外部链接的特定标识符的每个声明表示相同的对象或函数。在一个翻译单元内,具有内部链接的标识符的每个声明表示相同的对象或函数。

其中,“翻译单元” 是经过预处理的源文件。

GCC 如何为 ELF(Linux)实现它?

使用 STB_LOCAL 绑定。

如果我们编译:

int i = 0;
static int si = 0;

使用以下命令解析符号表:

readelf -s main.o

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 si
 10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 i

因此,绑定是它们之间唯一的显着区别。Value只是它们偏移.bss节,所以我们希望它们有所不同。

STB_LOCAL在ELF规范中有文档,网址为http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html:

  

STB_LOCAL本地符号对于包含定义的对象文件外部不可见。同名的本地符号可以在多个文件中存在而互不干扰

这使它成为代表static的完美选择。

没有static的变量为STB_GLOBAL,规范说:

  

当链接编辑器组合几个可重定位对象文件时,它不允许具有相同名称的STB_GLOBAL符号的多个定义。

这与多个非静态定义的链接错误相一致。

如果我们使用-O3增强优化,则si符号将从符号表中完全删除:它无论如何都无法从外部使用。待办事项:为什么在没有优化时仍然将静态变量保留在符号表中?它们可以用于任何东西吗?也许是用于调试。

另请参阅

C++匿名命名空间

在C ++中,您可能希望使用匿名名称空间而不是静态,这可以实现类似的效果,并进一步隐藏类型定义:Unnamed/anonymous namespaces vs. static functions


41

这要视情况而定:

int foo()
{
   static int x;
   return ++x;
}

这个函数会返回1、2、3等,变量不在堆栈上。

a.c:

static int foo()
{
}

这意味着此函数仅在此文件中具有作用域。因此,a.c和b.c可以具有不同的foo()函数,并且foo不会被暴露给共享对象。因此,如果您在a.c中定义了foo,则无法从b.c或任何其他位置访问它。

在大多数C库中,所有“私有”函数都是静态的,而大部分“公共”函数则不是。


20
+1 是因为提到 x 不在栈或堆上,而是在静态内存空间上。 - RoundPi
1
@Gob00st 静态内存空间?你是指“数据段”吗? - Yousha Aleayoub
@YoushaAleayoub 请参阅 这个链接这个链接 - Ekrem Dinçel
在大多数C库中,“私有”函数都是静态的,而“公共”函数则不是。您好,我对此有一个问题,您说“大多数”,我想知道如何让“静态”函数表现为公共函数。 - Sekomer
1
如果一个静态函数指针通过作为另一个函数的返回值或者通过被设置为结构体成员变量的函数指针而“逃逸”,那么它就会被视为函数指针。 - Eljay

30

人们总是说C语言中的“static”有两个意思。我提供了一种新的看法,使其具有单一意义:

  • 将“static”应用于一个变量会强制该变量具有两个属性: (a)它在当前范围之外不可见;(b)它是持久的。

它似乎有两个含义的原因是,在C语言中,每个可以应用“static”的变量已经拥有这两个属性之一,因此似乎特定的用法只涉及另一个属性。

例如,请考虑变量。函数外声明的变量已经持久存在(在数据段中),因此应用“static”只能使它们在当前作用域之外不可见(编译单元)。相反,函数内声明的变量已经无法在当前作用域之外可见(函数),因此应用“static”只能使它们持久存在。

将“static”应用于函数就像将其应用于全局变量一样——代码必须是持久的(至少在这种语言中),因此只能改变可见性。

注意:这些评论仅适用于C语言。在C ++中,将“static”应用于类方法确实赋予了该关键字不同的含义。C99数组参数扩展也是如此。


1
你的(a)最多是多余的。在其范围之外根本看不到任何变量。这就是作用域的定义。你所说的是C标准中所谓的链接static为标识符提供了内部链接 - Jens

19

static 在不同的上下文中有不同的含义。

  1. 在 C 函数中,您可以声明一个静态变量。这个变量只在函数内部可见,但它的行为类似于全局变量,因为它只被初始化一次并且保留其值。在这个例子中,每次调用 foo() 都会打印一个递增的数字。静态变量只被初始化一次。

    void foo ()
    {
    static int i = 0;
    printf("%d", i); i++
    }
    
    另一种使用静态的方法是在.c文件中实现函数或全局变量,但不希望其符号在生成的.obj文件之外可见。例如:
  2. static void foo() { ... }
    

18

来自维基百科:

在C编程语言中,使用static关键字可以让全局变量和函数的作用域限定于所在文件。对于局部变量,使用static可以将其存储在静态分配的内存中而不是自动分配的内存中。虽然语言并未规定这两种内存类型的实现方式,但通常情况下,静态分配的内存会被保留在程序的数据段中,在编译时进行分配,而自动分配的内存则通常作为一个瞬时调用栈进行实现。


维基百科最糟糕的地方。静态集合链接,而不是作用域。理解这种差异至关重要。 - Jens
4
@Jens 没有人提出关于 static 的问题会知道 linkage 意味着什么。但是,作用域的概念几乎所有语言都具备,因此任何人都应该能够根据这个描述大致理解 static 如何影响对象。出于同样的原因,它提到了“包含文件”而不是“当前编译单元”。 - natiiix
@natiiix 链接不等同于作用域。static并不设置作用域。即使“包含文件”也是错误的,因为作用域仅从声明符的末尾开始,而不是从文件开头开始。维基百科的条目引用如此误导,以至于会让特朗普都脸红。 - Jens
2
@Jens,这并不重要。就所有目的而言,“static”使全局变量成为文件本地变量,并将其从真正的全局范围中移除。当一个简单的问题期望得到简单、直接的答案时,没有必要使用花哨的术语。当然,这并不完全正确,但它有助于每个人理解一般思想,这比术语细微差别更重要。 - natiiix

11
我不想再回答一个老问题,但我认为没有人提到K&R如何在“C编程语言”第A4.1节中解释它。
简而言之,static这个词有两种含义:
  1. 静态是两种存储类之一(另一种是自动)。 静态对象在调用之间保持其值。 在所有块外部声明的对象始终是静态的,不能使其自动化。
  2. 但是,当使用static 关键字(非常强调在代码中使用它作为关键字)进行声明时,它会给该对象赋予内部链接,因此它只能在该翻译单元内使用。 但是,如果关键字在函数中使用,则更改对象的存储类(该对象仅在该函数内可见)。 static的相反是extern关键字,它会给对象分配外部链接。
Peter Van Der Linden在“ Expert C Programming”中给出了这两个含义:
  • 在函数内部,在调用之间保留其值。
  • 在该文件中仅可见于函数级别。

有第三种存储类 - 寄存器。一些人还提出第四种存储类 - 已分配,用于malloc和相似函数返回的存储。 - Jens
@Jens的'register'只是对编译器的提示;寄存器存储无法从C源代码内部强制执行。因此,我不认为它是一种存储类。 - GermanNerd
2
@GermanNerd,恐怕ISO C标准不同意您的观点,因为它明确将register作为存储类别说明符(C99 6.7.1 存储类别说明符)。而且它不仅仅是一个提示,例如,无论编译器是否分配寄存器,都不能对具有存储类别register的对象应用取地址运算符& - Jens
@Jens 感谢您提醒我关于 & 的问题。我可能做了太多的 C++……无论如何,“register”虽然是一个存储类别限定符,但实际上编译器很可能会为(无用的)“auto”限定符和“register”限定符创建相同的机器码。因此,唯一剩下的就是源代码级别的限制,即不能取地址。顺便说一句,这个小讨论让我发现了 Netbeans 中的一个错误;自从我的最新更新以来,在新的 C 项目中,它默认使用 g++ 工具链! - GermanNerd

11

如果在函数中声明变量为静态(static),那么该变量的值将不会存储在函数调用堆栈中,当再次调用该函数时,该变量仍然可用。

如果在全局范围内声明变量为静态(static),那么其作用域将被限制在声明它的文件内。这比普通的全局变量更加安全,因为普通全局变量可以在整个程序中读取和修改。


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