"静态"在C中意味着什么?

1062 c syntax static

static在C代码中看到了不同地方使用的单词; 这就像C#中的静态函数/类(其中实现是跨对象共享的)?

Eli*_*sky 1446

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

这对于函数需要在调用之间保持某种状态并且您不想使用全局变量的情况很有用.但要注意,应该非常谨慎地使用此功能 - 它使您的代码不是线程安全的,更难理解.

(2)广泛用作"访问控制"功能.如果您有.c文件实现某些功能,它通常只向用户公开一些"公共"功能.应该完成其余的功能static,以便用户无法访问它们.这是封装,一个很好的做法.

引用维基百科:

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

有关详细信息,请参见此处此处.

并回答你的第二个问题,它不像在C#中.

但是,在C++中,static还用于定义类属性(在同一个类的所有对象之间共享)和方法.在C中没有类,所以这个功能是无关紧要的.

  • Pax,OP不知道静态,所以你建议让他陷入编译单元和文件之间的区别?:-) (176认同)
  • 有点迂腐,它是编译单元,而不是文件. (153认同)
  • 编译单元是编译器看到的单个文件.您的.c文件可能包含其他.c文件,但在预处理器整理出包含之后,编译器最终只会看到一个"编译单元". (136认同)
  • @robUK:编译器甚至不知道.h文件 - 这些文件被合并到预处理器中的.c文件中.所以是的,你可以说.c文件,其中包含所有标题,是一个单独的编译单元. (80认同)
  • @TonyD编译器进行编译.预处理器进行预处理.将工具链称为"编译器"不会改变它是什么或它做什么. (7认同)
  • @TonyD可能令人困惑,但它是编译工作的方式.它通常可能是一个`.c`和一堆头文件,但是魔鬼总是处于*不典型的状态. (6认同)
  • @TonyD抱歉,但你错了.预处理和编译是完全不同的步骤,了解它们的不同之处至关重要. (6认同)
  • David,如果您是 C 语言新手,这个问题根本不会困扰您。无论如何,它不会困扰 99.9% 的 C 程序员。 (3认同)
  • 因此,如果您在一个头文件中定义一个包含在文件中的静态文件,但在该文件中另一次定义相同的静态文件,那么这是一个错误,因为这两个定义出现在同一个翻译单元中. (3认同)
  • 这让我思考.编译单元是否仅包含其*.c文件.例如,如果我有一个名为foo.c的文件和一个名为foo.h的头文件.它们中的2个是否包含1个编译单元?或者foo.c是否是一个没有foo.h的完整编译单元? (2认同)
  • @user1599964:C 本身没有“私有”的概念。然而,当函数和变量在 C 文件中被设为静态时,它们本质上是这个 C 文件的私有的,因为它们不能从其他 C 文件直接访问。 (2认同)
  • @peterph:然后可以通过说"翻译单元由在命令行上为编译器指定的源文件组成,每个#include(直接或间接)扩展到位"来解决.我的观点仍然是,充分描述"翻译单元"比制作"编译单元是编译器看到的单个文件"之类的语句更少误导或混淆.当没有任何单一的合并"文件"(在磁盘上,或者甚至在编译器的内存中)时."静态......仅在其声明的文件中被"看到" - 也具有误导性. (2认同)
  • 关于静态函数部分,已经有一个很好的问答:[C中的静态函数](http://stackoverflow.com/q/5319361/2157640) (2认同)
  • 为什么在不同的意义上使用相同的关键字?一次用于存储类型,其他用于限制变量范围。他们无法获得其他关键字!C 不必要地变得复杂,新手难以理解。 (2认同)
  • @paxdiablo如果您是真正的学徒,可以将其称为“翻译单位”;) (2认同)
  • 维基百科的术语是垃圾^错误,应该删除。`static` 不会“设置范围”(范围由声明在块内部或外部的位置决定)。`static` 将标识符的*链接*设置为*内部链接*,这意味着来自其他翻译单元的标识符无法引用它。这使得在另一个源文件中可以有具有相同名称的不同对象。 (2认同)

dre*_*lax 220

还有一个用途未在此处介绍,它作为数组类型声明的一部分作为函数的参数:

int someFunction(char arg[static 10])
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

在此上下文中,它指定传递给此函数的参数必须是一个类型数组,char其中至少包含10个元素.有关详情,请在此处查看我的问题.

  • @Qix - 这是C99中给`static`赋予的一个新的重载含义.它已经超过十五年了,但并非所有的编译器都采用了C99的所有功能 - 所以C99作为一个整体仍然未知. (19认同)
  • @jamieb:C没有数组参数,但是这个特定的语法意味着函数希望`arg [0]`到`arg [9]`有值(这也暗示函数不接受空指针) ).编译器可以某种方式利用这些信息进行优化,静态分析器可以利用这些信息来确保函数永远不会被赋予空指针(或者如果它可以告诉,一个数组的元素少于指定的数组). (13认同)
  • 我不认为C有数组参数?Linus Torvalds愤怒地咆哮着这样做的人. (2认同)

cmc*_*nty 162

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

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

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

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

  • 这是关于C.C中没有私人/公共. (40认同)
  • @ user1599964虽然在C中没有`private`,但你的类比很好:静态使得某些文件对于给定文件是"私有的".C中的文件经常映射到C++中的类. (16认同)
  • 那么"静态功能"和"私人功能"是否意味着同样的事情?同样是"静态全局变量"和"私有全局变量"是一回事吗? (7认同)

Cir*_*四事件 58

多文件变量范围示例

这里我将说明static如何影响多个文件中函数定义的范围.

AC

#include <stdio.h>

/*
Undefined behavior: already defined in main.
Binutils 2.24 gives an error and refuses to link.
https://stackoverflow.com/questions/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("");
}
Run Code Online (Sandbox Code Playgroud)

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

GitHub上游.

编译并运行:

gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
Run Code Online (Sandbox Code Playgroud)

输出:

m()
i = 1
si = 1

m()
i = 2
si = 2

a()
i = 3
si = 1

a()
i = 4
si = 2
Run Code Online (Sandbox Code Playgroud)

解释

  • 有两个单独的变量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;
Run Code Online (Sandbox Code Playgroud)

并用以下符号反汇编符号表:

readelf -s main.o
Run Code Online (Sandbox Code Playgroud)

输出包含:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  5: 0000000000000004     4 OBJECT  LOCAL  DEFAULT    4 si
 10: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 i
Run Code Online (Sandbox Code Playgroud)

所以绑定是它们之间唯一的重要区别.Value只是它们偏移到该.bss部分,所以我们期望它有所不同.

STB_LOCAL有关ELF规范的信息,请访问http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html:

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

这使它成为一个完美的选择static.

没有静态的变量是STB_GLOBAL,并且规范说:

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

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

如果我们使用了优化-O3,则si符号将完全从符号表中删除:无论如何都不能从外部使用它.TODO为什么在没有优化时根据符号表保留静态变量?他们可以用于任何事情吗?也许是为了调试.

也可以看看

C++匿名命名空间

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

  • 迄今为止最好的答案! (2认同)

Art*_*yom 37

这取决于:

int foo()
{
   static int x;
   return ++x;
}
Run Code Online (Sandbox Code Playgroud)

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

AC:

static int foo()
{
}
Run Code Online (Sandbox Code Playgroud)

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

在大多数C库中,所有"私有"函数都是静态的,而大多数"公共"函数则不是.

  • 提及x不在堆栈或堆上的+1.它在静态内存空间. (18认同)

小智 22

人们一直说C中的"静态"有两个含义.我提供了一种查看它的替代方法,它赋予它一个单一含义:

  • 对项目应用'static'会强制该项具有两个属性:(a)在当前范围之外不可见; (b)坚持不懈.

它似乎有两个含义的原因是,在C中,可以应用"静态"的每个项目都具有这两个属性中的一个,所以看起来好像这个特定用法只涉及另一个.

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

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

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


Osc*_*Ryz 15

来自维基百科:

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

  • @Jens 没有人问有关“静态”的问题会知道“链接”的含义。然而,作用域的概念几乎对所有语言来说都是通用的,因此任何人都应该能够根据这个描述粗略地理解“static”如何影响对象。出于同样的原因,它提到“包含文件”而不是“当前编译单元”。 (4认同)
  • @Jens 但这根本不重要。出于所有意图和目的,“static”使全局变量成为文件的本地变量,并将它们从真正的全局范围中删除。当提出一个简单的问题并期望得到简单、直接的答案时,使用花哨的术语是没有意义的。当然,这并不完全正确,但它可以帮助每个人理解总体概念,这比一些术语的细微差别更重要。 (2认同)

m-s*_*arp 15

static 在不同的背景下意味着不同的东西.

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

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

    static void foo() { ... }
    
    Run Code Online (Sandbox Code Playgroud)


Jon*_*son 7

如果在函数static中声明一个变量,它的值将不会存储在函数调用堆栈中,并且在再次调用该函数时仍然可用.

如果声明一个全局变量static,其范围将限制在您声明它的文件中.这比常规全局更安全,可以在整个程序中读取和修改.


jig*_*uff 7

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

简而言之,static这个词有两个含义:

  1. Static是两个存储类之一(另一个是自动存储).静态对象在调用之间保持其值.在所有块之外声明的对象始终是静态的,不能自动生成.
  2. 但是,当static 关键字(强调它在代码中用作关键字)与声明一起使用时,它会为该对象提供内部链接,因此它只能在该转换单元中使用.但是如果关键字在函数中使用,它会更改对象的存储类(该对象只能在该函数中可见).与static相反的是extern关键字,它为对象提供外部链接.

Peter Van Der Linden在"专家C编程"中给出了这两个含义:

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

  • @GermanNerd恐怕ISO C标准不同意你的观点,因为它明确地使“register”成为*存储类说明符*(C99 6.7.1存储类说明符)。它不仅仅是一个提示,例如,无论编译器是否分配寄存器,都不能在存储类为“register”的对象上应用取址运算符“&amp;”。 (2认同)

Chr*_*Bao 7

Share what I learned about this point.

In C static is a declaration specifier, which falls into three categories:

  • storage classes: there are four classes: auto, static, extern and register.
  • type qualifiers: like keywords: const, volatile, etc.
  • type specifiers: like keywords: void, char, short, int, etc.

So static is a storage classes. It will determine the following three properties of each variable in a C program.

  • storage duration: means when memory is allocated for the variable and when the memory is released. A variable with static storage duration stays at the same memory location as long as the program is running.
  • scope: means the portion of the program text in which the variable can be accessed. A static variable has a file scope instead of a block scope.
  • linkage: means the extent to which the variable can be shared by different parts(or files) of a program. If a static variable is declared inside a block then it has no linkage. If a static variable is declared outside blocks, then it has internal linkage. Internal linkage makes it accessible in a single file.

The static storage class has a different effect on a variable depending on it is declared outside a block or inside a block. Need to consider case by case.


小智 6

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

在任何其他范围内,它声明一个对象,该对象将在输入特定范围的不同时间之间保留其值.例如,如果在过程中对del进行了delcared:

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

   i++;
}
Run Code Online (Sandbox Code Playgroud)

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


Yag*_*gel 6

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

void func(void) {
    static int count; // If you don't declare its value, it is initialized with zero
    printf("%d, ", count);
    ++count;
}

int main(void) {
    while(true) {
        func();
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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


ant*_*009 5

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

static int my_variable;
Run Code Online (Sandbox Code Playgroud)

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

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

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


Sta*_*owl 5

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

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