在C语言中“静态”是什么意思?


1135

我已经static在C代码的不同地方看到过这个词;这就像C#中的静态函数/类(实现在对象之间共享)吗?



15
从标题末尾@Lundin删除“在C程序中”的原因是什么?在存在标签c的情况下,这有点多余,但是它使我可以更快地查看分类,而无需检查标签。当我从可能也包含有关其他语言的问题(例如静态搜索或Google搜索)的方向提出问题时,这种冗余非常舒适。
Palec

5
@Palec有一个SO策略,标签列表中存在的项目在标题中是多余的。该站点将自动将C附加到实际的网站。对于“ C static”,Google给出了最佳答案。之所以要对此进行更改,是因为此问题现在已成为SO C语言常见问题解答的一部分,并且所有添加的帖子都会有所修饰。
伦丁

1
@Lundin我更喜欢在标题中保留“ C”,因为SO仅在标题后附加一个标签(最常见?)。如果某天“语法”比C提出更多的问题(既然是跨语言的事情)怎么办?我宁愿使用的外显行为:-)编辑:啊,但有一个问题元另有说法:meta.stackexchange.com/questions/19190/...
西罗桑蒂利冠状病毒审查六四事件法轮功

Answers:


1519
  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,以便用户无法访问它们。这是封装,是一种好习惯。

引用维基百科

在C编程语言中,static与全局变量和函数一起使用以将其范围设置为包含文件。在局部变量中,static用于将变量存储在静态分配的内存中,而不是自动分配的内存中。虽然该语言没有规定哪种类型的内存的实现,但是静态分配的内存通常在编译时保留在程序的数据段中,而自动分配的内存通常实现为瞬时调用堆栈。

要回答您的第二个问题,它与C#中的情况不同。

但是,在C ++中,static它也用于定义类属性(在同一类的所有对象之间共享)和方法。在C中没有类,因此此功能无关紧要。


179
Pax,OP不了解静态,因此您建议让他陷入编译单元和文件之间的差异吗?:-)
Eli Bendersky

138
编译单元是编译器看到的单个文件。您的.c文件可能包含其他.c文件,但是在预处理程序对包含文件进行整理之后,编译器最终只会看到一个“编译单元”。
伊莱·班德斯基

81
@robUK:编译器甚至不知道.h文件-它们在预处理器中组合为.c文件。因此,可以的说,.c文件及其所有标头都包含在一个编译单元中。
伊莱·班德斯基

6
@TonyD可能令人困惑,但这是编译的工作方式。它通常可能是一个.c头文件堆,但是魔鬼总是处在典型的地方。
彼得2014年

7
@TonyD编译器进行编译。预处理器进行预处理。将工具链称为“编译器”不会改变它的作用或作用。
Miles Rout 2014年

231

这里没有涉及另一种用途,它是数组类型声明的一部分,用作函数的参数:

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

在这种情况下,这指定传递给此函数的参数必须是类型数组,char其中至少包含10个元素。有关更多信息,请在此处查看我的问题。


3
我不认为C有数组参数吗?Linus Torvalds对这样做的人感到愤怒。
suprjami

13
@jamieb:C没有数组参数,但是这种特定的语法意味着该函数希望arg[0]通过arg[9]它具有值(这也意味着该函数不接受空指针)。编译器可以以某种方式利用此信息进行优化,而静态分析器可以利用该信息来确保函数永远不会被赋予空指针(或者,如果可以告诉它,则其数组比指定的元素要少)。
dreamlax

19
@Qix-这是staticC99中赋予的新的重载含义。它已经存在了十五年半了,但是并不是所有的编译器作者都接受了C99的所有功能-因此,整个C99在很大程度上仍然是未知的。
快乐的小孩子午睡

@suprjami我不确定100%“数组参数”是什么意思,但是,如果您的意思int arr[n];V99(可变长度数组),它是在C99中添加的。这是你的意思吗?
拉斯塔杰迪

170

简短的回答... 这取决于。

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

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

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


8
那么,“静态功能”和“私有功能”是同一意思吗?同样,“静态全局变量”和“私有全局变量”是同一件事吗?
user1599964

40
这是关于C的。C中没有私人/公共场所
。– chris

19
@ user1599964尽管privateC语言中没有,但您的类比很好:静态使事物“私有”到给定文件。C中的文件通常映射到C ++中的类。
Ciro Santilli冠状病毒审查六四事件法轮功

66

多文件变量作用域示例

在这里,我说明了静态如何影响多个文件中函数定义的范围。

交流电

#include <stdio.h>

/*
Undefined behavior: already defined in main.
Binutils 2.24 gives an error and refuses to link.
/programming/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*int i = 0;*/

/* Works in GCC as an extension: https://stackoverflow.com/a/3692486/895245 */
/*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("");
}

main.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上游

编译并运行:

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意味着internal linkage

如果对象或函数的文件作用域标识符的声明包含静态的存储类说明符,则该标识符具有内部链接。

和6.2.2 / 2表示,internal linkage其行为类似于我们的示例:

在构成整个程序的一组翻译单元和库中,带有外部链接的特定标识符的每个声明表示相同的对象或功能。在一个翻译单元中,带有内部链接的标识符的每个声明都表示相同的对象或功能。

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

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_LOCALhttp://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html的ELF规范中有记录:

STB_LOCAL本地符号在包含其定义的目标文件之外不可见。多个文件中可能存在相同名称的本地符号,而不会互相干扰

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

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

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

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

如果使用来启动优化-O3,则si符号将从符号表中完全删除:无论如何都不能从外部使用它。TODO为什么在没有优化的情况下将静态变量完全保留在符号表上?它们可以用于任何用途吗?也许用于调试。

也可以看看

C ++匿名名称空间

在C ++中,您可能希望使用匿名名称空间而不是静态名称空间,这可以达到类似的效果,但是会进一步隐藏类型定义:未命名/匿名名称空间与静态函数


39

这取决于:

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

该函数将返回1、2、3等。---变量不在堆栈中。

交流电:

static int foo()
{
}

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

在大多数C库中,所有“私有”功能都是静态的,而大多数“公共”功能不是静态的。


18
+1表示x不在堆栈或堆上。它在静态内存空间上。
Gob00st 2012年

1
@ Gob00st静态内存空间?你的意思是“数据段” ...?
Yousha Aleayoub

24

人们一直说C语言中的“静态”有两个含义。我提供了另一种查看方式,使它具有单一含义:

  • 对项目应用“静态”会强制该项目具有两个属性:(a)在当前范围之外不可见;(b)它是持久的。

它似乎有两个含义的原因是,在C语言中,可能对其应用“静态”的每个项目都已经具有这两个属性之一,因此似乎该特定用法仅涉及另一个。

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

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

注意:这些注释仅适用于C。在C ++中,将'static'应用于类方法确实为关键字赋予了不同的含义。对于C99数组参数扩展类似。


您的(a)充其量是多余的。在其范围之外,没有任何可见的变量。那只是范围的定义。您的意思在C标准中称为链接static给标识符提供内部链接
詹斯(Jens)

16

从维基百科:

在C编程语言中,static与全局变量和函数一起使用以将其范围设置为包含文件。在局部变量中,static用于将变量存储在静态分配的内存中,而不是自动分配的内存中。虽然该语言没有规定哪种类型的内存的实现,但是静态分配的内存通常在编译时保留在程序的数据段中,而自动分配的内存通常实现为瞬时调用堆栈。


16

static 在不同背景下意味着不同的事物。

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

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

    static void foo() { ... }

8

如果在函数静态中声明变量,则其值将不会存储在函数调用堆栈中,并且在再次调用该函数时仍然可用。

如果将全局变量声明为静态,则其范围将限于声明它的文件内。这比可以在整个程序中读取和修改的常规全局变量稍微安全些。


8

我讨厌回答一个老问题,但是我认为没有人在“ C编程语言”的A4.1节中提到过K&R如何解释它。

简而言之,“静态”一词具有两种含义:

  1. 静态是两个存储类之一(另一个是自动的)。静态对象在两次调用之间保留其值。在所有块外部声明的对象始终是静态的,不能使其成为自动的。
  2. 但是,当static 关键字(将重点放在代码中作为关键字使用时)与声明一起使用时,它将为对象提供内部链接,因此只能在该翻译单元中使用。但是,如果在函数中使用关键字,则它将更改对象的存储类(无论如何,该对象仅在该函数中可见)。与static相反的是extern关键字,它为对象提供外部链接。

Peter Van Der Linden在“专家C编程”中给出了这两种含义:

  • 在函数内部,在两次调用之间保留其值。
  • 在功能级别,仅在此文件中可见。

还有第三种存储类,register。某些人还为malloc和friends返回的存储分配了第四个存储类,allocate
詹斯

@Jens'register'只是对编译器的提示;不能从C源内部强制执行寄存器存储。因此,我不会将其视为存储类。
GermanNerd

1
@GermanNerd恐怕ISO C标准不同意您的观点,因为它显然使register存储类说明符(C99 6.7.1存储类说明符)。这不仅仅是提示,例如,无论编译器是否分配寄存器,您都不能&在存储类对象上应用地址运算符register
詹斯(Jens)

@Jens感谢您提醒我有关&的信息。我可能做了太多的C ++ .....无论如何,虽然“寄存器”是存储类说明符,但实际上,编译器可能会为(无用的)“自动”说明符创建与“寄存器”相同的机器代码。说明符。因此,唯一剩下的就是无法获取地址的源代码级别限制。顺便说一句,这个小小的讨论使我在Netbeans中发现了一个错误。自从我最新的更新以来,它默认为新C项目中的g ++工具链!
GermanNerd

6

在C语言中,static具有两种含义,具体取决于其使用范围。在全局范围内,当在文件级别声明对象时,意味着该对象仅在该文件中可见。

在任何其他范围内,它都声明一个对象,该对象将在输入特定范围的不同时间之间保留其值。例如,如果一个int在过程中被代入:

void procedure(void)
{
   static int i = 0;

   i++;
}

在第一次调用该过程时,“ i”的值将初始化为零,并且在以后每次调用该过程时,该值都会保留。如果打印'i',则会输出0、1、2、3,...的序列


5

重要的是要注意,函数中的静态变量在该函数的第一个条目处被初始化,并且即使在它们的调用完成之后也仍然存在。在使用递归函数的情况下,静态变量仅初始化一次,并且在所有递归调用中甚至在函数调用完成之后都将持续存在。

如果变量是在函数外部创建的,则意味着程序员只能在已声明该变量的源文件中使用该变量。


5

如果在mytest.c文件中声明:

static int my_variable;

然后只能从该文件中看到该变量。该变量无法导出到其他任何地方。

如果在函数内部声明,则变量的值将在每次调用该函数时保持其值。

静态函数无法从文件外部导出。因此,在*.c文件中,如果将函数和变量声明为静态,则它们将被隐藏。


4

C语言中的静态变量具有程序的生命周期。

如果在函数中定义,则它们具有局部作用域,即只能在这些函数内部访问它们。静态变量的值在函数调用之间保留。

例如:

void function()
{
    static int var = 1;
    var++;
    printf("%d", var);
}

int main()
{
    function(); // Call 1
    function(); // Call 2
}

在上面的程序中 var将其存储在数据段中。它的生命周期是整个C程序。

函数调用1之后var变为2。函数调用2之后var变为3。

var函数调用之间不会破坏的值。

如果var在非静态变量和局部变量之间,则将其存储在C程序的堆栈段中。由于函数返回后会破坏函数的堆栈框架,因此的值var也会被破坏。

初始化的静态变量存储在C程序的数据段中,而未初始化的静态变量存储在BSS段中。

有关静态的另一信息:如果变量是全局和静态的,则它具有C程序的生命周期,但具有文件作用域。它仅在该文件中可见。

尝试一下:

文件1.c

static int x;

int main()
{
    printf("Accessing in same file%d", x):
}

文件2.c

    extern int x;
    func()
    {
        printf("accessing in different file %d",x); // Not allowed, x has the file scope of file1.c
    }

run gcc -c file1.c

gcc -c file2.c

现在尝试使用以下方法链接它们:

gcc -o output file1.o file2.o

因为x具有file1.c的文件范围,并且链接器将无法解析对file2.c中使用的变量x的引用,这将导致链接器错误。

参考文献:

  1. http://en.wikipedia.org/wiki/Translation_unit_(编程)
  2. http://en.wikipedia.org/wiki/Call_stack

我知道数据是持久性的,这意味着在每次函数调用后数据都不会丢失,但是为什么每次都不能将static int var = 1;值改回一个值
Eames

3

静态变量是可以在函数中使用的特殊变量,它可以保存两次调用之间的数据,而不会在两次调用之间删除数据。例如:

void func(){
    static int count; // If you don't declare its value, the value automatically initializes to zero
    printf("%d, ", count);
    ++count;
}

void main(){
    while(true){
        func();
    }
}

输出:

0、1、2、3、4、5,...


您可以printf("%d, ", count); count++;用`printf(“%d,”,count ++)代替(没关系:P)。
拉斯塔杰迪

2

静态变量值在不同的函数调用之间持续存在,并且其范围限于本地块,而静态var始终以值0初始化


2

有两种情况:

(1)声明的局部变量static:分配在数据段中而不是堆栈中。再次调用该函数时,其值将保留。

(2)声明的全局变量或函数static:外部编译单元不可见(即链接期间符号表中的局部符号)。


1

静态变量即使在超出范围后也具有保留其值的属性!因此,静态变量将其先前值保留在其先前范围中,并且不会在新范围中再次初始化。

例如看一下-程序运行时,静态int变量保留在内存中。当声明了该变量的函数调用结束时,普通或自动变量将被破坏。

#include<stdio.h> 
int fun() 
{ 
  static int count = 0; 
  count++; 
  return count; 
} 

int main() 
{ 
  printf("%d ", fun()); 
  printf("%d ", fun()); 
  return 0; 
}

这将输出:1 2

由于1声明为静态而保留在内存中

如果未显式初始化静态变量(如全局变量),则初始化为0。例如,在下面的程序中,x的值打印为0,而y的值则是垃圾。有关更多详细信息,请参见此内容。

#include <stdio.h> 
int main() 
{ 
    static int x; 
    int y; 
    printf("%d \n %d", x, y); 
}

这将输出:0 [some_garbage_value]

这些是我发现的主要功能,上面没有为新手解释!


-1

在C编程中,static是保留关键字,它既控制生存期,又控制可见性。如果我们将变量声明为函数内部的静态变量,则该变量仅在整个函数中可见。在这种用法中,此静态变量的生存期将在函数调用时开始,并在执行该函数后销毁。您可以看到以下示例:

#include<stdio.h> 
int counterFunction() 
{ 
  static int count = 0; 
  count++; 
  return count; 
} 

int main() 
{ 
  printf("First Counter Output = %d\n", counterFunction()); 
  printf("Second Counter Output = %d ", counterFunction()); 
  return 0; 
}

上面的程序将为我们提供以下输出:

First Counter Output = 1 
Second Counter Output = 1 

因为一旦我们调用该函数,它将初始化count = 0。当我们执行时,counterFunction它将破坏count变量。


2
>以上程序将为我们提供以下输出:第一个计数器输出= 1第二个计数器输出= 1 <不正确。静态变量仅初始化一次。因此输出将是1,然后是2,依此类推。
GermanNerd
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.