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

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

答案

  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 中没有类,因此此功能无关紧要。

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

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

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

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

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

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

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

多文件变量作用域示例

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

交流电

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

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

这取决于:

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

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

交流电:

static int foo()
{
}

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

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

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

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

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

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

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

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

从维基百科:

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

static在不同的上下文中意味着不同的事物。

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

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

    static void foo() { ... }

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

简而言之,静态一词有两个含义:

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

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

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

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

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