ChatGPT解决这个技术问题 Extra ChatGPT

C中的“静态”是什么意思?

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

从标题末尾删除“在 C 程序中”@Lundin 的原因是什么?在存在标签 c 的情况下它有点多余,但它让我可以更快地看到分类,而无需检查标签。当我从可能包含其他语言问题的方向(例如 static 或 Google 搜索)提出问题时,这种冗余非常舒适。
@Lundin我更喜欢在标题中保留“C”,因为SO只在标题上附加一个标签(最常见的?)。如果有一天“语法”比 C 遇到更多的问题(因为它是跨语言的东西)怎么办?我宁愿使用显式行为:-) 编辑:啊,但有一个元问题另有说明:meta.stackexchange.com/questions/19190/…
static 的存储持续时间是直到程序结束,而不是直到范围结束。

a
ady_agar

函数内的静态变量在调用之间保持其值。静态全局变量或函数仅在其声明的文件中“可见”

如果您是新手,(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 用于将变量存储在静态分配的内存中,而不是自动分配的内存中。虽然语言没有规定这两种内存的实现方式,但静态分配的内存通常在编译时保留在程序的数据段中,而自动分配的内存通常实现为临时调用堆栈。

为了回答你的第二个问题,它不像在 C# 中。

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


Pax,OP不知道静态,所以你建议让他陷入编译单元和文件之间的区别? :-)
编译单元是编译器看到的单个文件。您的 .c 文件可能包含其他 .c 文件,但在预处理器整理出包含后,编译器最终只会看到一个“编译单元”。
@robUK:编译器甚至不知道 .h 文件 - 这些文件在预处理器中合并到 .c 文件中。所以是的,你可以说包含所有头文件的 .c 文件是一个单独的编译单元。
@TonyD 可能令人困惑,但这就是编译的工作方式。它通常可能是一个 .c 和一堆头文件,但魔鬼总是在典型的情况下。
@TonyD 编译器进行编译。预处理器进行预处理。将工具链称为“编译器”并不会改变它的本质或功能。
C
Community

这里没有介绍另外一种用途,即作为数组类型声明的一部分,作为函数的参数:

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

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


我不认为 C 有数组参数? Linus Torvalds 愤怒地抱怨人们这样做。
@jamieb:C 没有数组参数,但是这种特定的语法意味着该函数期望 arg[0]arg[9] 具有值(这也意味着该函数不接受空指针)。编译器可以以某种方式利用这些信息进行优化,而静态分析器可以利用这些信息来确保函数永远不会被赋予空指针(或者如果它可以告诉,一个元素少于指定元素的数组)。
@Qix -- 这是 C99 中赋予 static 的新的重载含义。它已有 15 多年的历史,但并非所有编译器编写者都接受了所有 C99 特性——因此 C99 作为一个整体在很大程度上仍然未知。
@suprjami 我不是 100% 确定您所说的 “数组参数” 是什么意思,但如果您的意思是 int arr[n];,那么这是一个 VLA(可变长度数组),这是在 C99 中添加的。这是你的意思吗?
这是否意味着,我不能将任何 char* 传递给这个函数,因为没有人知道它是否可以增加 10 ......我怀疑你的答案,虽然它很有趣。
S
Samuel Edwin Ward

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

静态定义的局部变量在函数调用之间不会丢失它们的值。换句话说,它们是全局变量,但作用域仅限于定义它们的局部函数。静态全局变量在定义它们的 C 文件之外不可见。静态函数在定义它们的 C 文件之外不可见。


那么“静态功能”和“私有功能”是否意味着同一件事?同样,“静态全局变量”和“私有全局变量”是一回事吗?
这是关于 C 的。C 中没有私有/公共。
@user1599964 尽管 C 中没有 private,但您的类比很好:静态使事物对给定文件“私有”。 C 中的文件通常映射到 C++ 中的类。
C
Ciro Santilli Путлер Капут 六四事

多文件变量范围示例

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

交流

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

主程序

#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 有两个单独的变量,每个文件一个

有一个共享变量

像往常一样,范围越小越好,所以如果可以的话,总是声明变量 static

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

关于它的标准是什么

C99 N1256 draft 6.7.1 “存储类说明符”说 static 是“存储类说明符”。

6.2.2/3 “标识符的链接”说 static 暗示 internal linkage

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

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

STB_LOCAL 局部符号在包含其定义的目标文件之外不可见。同名的本地符号可以存在于多个文件中,互不干扰

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

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

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

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

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

也可以看看

类似于静态函数:https://stackoverflow.com/a/30319812/895245

将 static 与 extern 进行比较,“相反”:如何使用 extern 在源文件之间共享变量?

C++ 匿名命名空间

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


C
Community

这取决于:

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

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

交流:

static int foo()
{
}

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

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


+1 提到 x 不在堆栈或堆上。它在静态内存空间上。
@Gob00st 静态内存空间?你的意思是“数据段”......?
@YoushaAleayoub 请参阅 thisthis
In most C libraries all "private" functions are static and most "public" are not. 您好,我对此有疑问,您说的是 most ,我想知道 static 函数如何表现得像 public
@Sekomer • 如果静态函数指针“转义”为另一个函数的函数指针返回值,或者通过结构中的成员变量设置为函数指针。
P
PMar

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

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

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

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

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

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


你的(a)充其量是多余的。在其范围之外没有任何变量是可见的。这只是范围的定义。您的意思是在 C 标准中称为 linkagestatic 为标识符提供内部链接
J
Jason Plank

static 在不同的上下文中表示不同的东西。

您可以在 C 函数中声明静态变量。此变量仅在函数中可见,但它的行为类似于全局变量,因为它只初始化一次并保留其值。在此示例中,每次调用 foo() 时,它都会打印一个递增的数字。静态变量只初始化一次。 void foo () { 静态 int i = 0; printf("%d", i); i++ } static 的另一个用途是当您在 .c 文件中实现函数或全局变量但不希望其符号在文件生成的 .obj 之外可见时。例如静态 void foo() { ... }


O
OscarRyz

来自维基百科:

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


最糟糕的维基百科。静态设置链接,而不是范围。了解差异至关重要。
@Jens 没有人提出关于 static 的问题会知道 linkage 是什么意思。但是,范围的概念几乎对所有语言都是通用的,因此任何人都应该能够根据此描述大致了解 static 如何影响对象。出于同样的原因,它提到了“包含文件”而不是“当前编译单元”。
@natiiix 链接不是范围。 static 未设置范围。甚至“包含文件”也是错误的,因为范围仅从声明符的末尾开始,而不是在文件的开头。引用的维基百科条目是如此具有误导性,这会让特朗普脸红。
@Jens 不过,这并不重要。出于所有意图和目的,static 使全局变量成为文件的本地变量,并将它们从真正的全局范围中删除。当一个简单的问题期望一个简单、直接的答案被问到时,使用花哨的术语是没有意义的。当然,这并不完全正确,但它可以帮助每个人理解总体思路,这比一些术语的细微差别更重要。
j
jigglypuff

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

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

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

Peter Van Der Linden 在《Expert C Programming》中给出了这两个含义:

在函数内部,在调用之间保留其值。

在函数级别,仅在此文件中可见。


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

如果你在函数中声明了一个静态变量,它的值将不会存储在函数调用堆栈中,当你再次调用该函数时仍然可用。

如果你声明一个全局变量 static,它的范围将被限制在你声明它的文件中。这比可以在整个程序中读取和修改的常规全局稍微安全一些。


s
schot

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

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

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

   i++;
}

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


F
Felipe Augusto

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

static int my_variable;

那么这个变量只能从这个文件中看到。该变量不能导出到其他任何地方。

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

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


J
Jens

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

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

输出:

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


S
Starhowl

重要的是要注意函数中的静态变量在第一次进入该函数时被初始化,并且即使在它们的调用完成后仍然存在;在递归函数的情况下,静态变量只被初始化一次,并且在所有递归调用中持续存在,甚至在函数调用完成之后也是如此。

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


P
Peter Mortensen

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 的引用。

参考:

http://en.wikipedia.org/wiki/Translation_unit_(programming) http://en.wikipedia.org/wiki/Call_stack


我知道数据是持久的,也就是说每次函数调用后都不会丢失,但是为什么static int var = 1;不每次都将值改回1
J
Jonathon

静态变量值在不同的函数调用之间保持不变,其范围仅限于本地块静态变量始终使用值 0 初始化


J
Jonny Kong

有2种情况:

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

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


e
era5tone

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

看看这个例子 - 当程序运行时,一个静态 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]

这些是我发现上面没有为新手解释的主要内容!


C
Chris Tang

在 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 变量。


>上面的程序会给我们这个输出:第一个计数器输出 = 1 第二个计数器输出 = 1 < 不正确。静态变量只初始化一次。所以输出将是 1,然后是 2,以此类推。
全局变量和静态变量初始化为 0,您不应在每个函数调用中将它们重新分配为零。