我在C语言代码中看到了static
这个词,它是否像C#中的静态函数/类(其实现被多个对象共用)?
我在C语言代码中看到了static
这个词,它是否像C#中的静态函数/类(其实现被多个对象共用)?
如果你是初学者,那么(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中没有类,因此这个特性是无关紧要的。
.c
文件和一堆头文件,但魔鬼常常藏在不典型的地方。 - peterph这里还有一种用法未被涵盖,即作为数组类型声明的一部分,作为函数参数:
int someFunction(char arg[static 10])
{
...
}
在这个上下文中,这指定传递给此函数的参数必须是至少包含10个元素的char
类型数组。有关更多信息,请参见我在这里的问题。
arg[0]
到arg[9]
的数组元素有值(这也意味着该函数不接受空指针)。编译器可以利用这些信息进行优化,静态分析工具可以利用这些信息确保函数永远不会被传入空指针(或者如果能判断,数组元素小于指定值)。 - dreamlaxstatic
新的多重含义。它已经有十五年以上的历史了,但并不是所有编译器都支持C99的所有特性,因此C99作为一个整体仍然很少被人所熟知。 - Happy Green Kid Napsint arr[n];
,那么这就是一个C99可变长度数组(VLA)。你是指这个吗? - RastaJedi简短回答是:要看情况而定。
静态定义的本地变量在函数调用之间不会丢失其值。换句话说,它们是全局变量,但作用域仅限于它们所定义的本地函数。
静态全局变量在定义它们的C文件之外不可见。
静态函数在定义它们的C文件之外不可见。
多文件变量作用域示例
这里我将演示静态变量如何影响跨多个文件的函数定义作用域。
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;
}
编译并运行:
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
符号将从符号表中完全删除:它无论如何都无法从外部使用。待办事项:为什么在没有优化时仍然将静态变量保留在符号表中?它们可以用于任何东西吗?也许是用于调试。
另请参阅
static
函数的类比:https://dev59.com/GHRB5IYBdhLWcg3wpoxm#30319812static
与extern
进行比较,后者实现“相反的”效果:How do I use extern to share variables between source files?C++匿名命名空间
在C ++中,您可能希望使用匿名名称空间而不是静态,这可以实现类似的效果,并进一步隐藏类型定义:Unnamed/anonymous namespaces vs. static functions
这要视情况而定:
int foo()
{
static int x;
return ++x;
}
这个函数会返回1、2、3等,变量不在堆栈上。
static int foo()
{
}
这意味着此函数仅在此文件中具有作用域。因此,a.c和b.c可以具有不同的foo()
函数,并且foo不会被暴露给共享对象。因此,如果您在a.c中定义了foo,则无法从b.c
或任何其他位置访问它。
在大多数C库中,所有“私有”函数都是静态的,而大部分“公共”函数则不是。
人们总是说C语言中的“static”有两个意思。我提供了一种新的看法,使其具有单一意义:
它似乎有两个含义的原因是,在C语言中,每个可以应用“static”的变量已经拥有这两个属性之一,因此似乎特定的用法只涉及另一个属性。
例如,请考虑变量。函数外声明的变量已经持久存在(在数据段中),因此应用“static”只能使它们在当前作用域之外不可见(编译单元)。相反,函数内声明的变量已经无法在当前作用域之外可见(函数),因此应用“static”只能使它们持久存在。
将“static”应用于函数就像将其应用于全局变量一样——代码必须是持久的(至少在这种语言中),因此只能改变可见性。
注意:这些评论仅适用于C语言。在C ++中,将“static”应用于类方法确实赋予了该关键字不同的含义。C99数组参数扩展也是如此。
static
为标识符提供了内部链接。 - Jensstatic
在不同的上下文中有不同的含义。
在 C 函数中,您可以声明一个静态变量。这个变量只在函数内部可见,但它的行为类似于全局变量,因为它只被初始化一次并且保留其值。在这个例子中,每次调用 foo()
都会打印一个递增的数字。静态变量只被初始化一次。
void foo ()
{
static int i = 0;
printf("%d", i); i++
}
另一种使用静态的方法是在.c文件中实现函数或全局变量,但不希望其符号在生成的.obj文件之外可见。例如: static void foo() { ... }
来自维基百科:
在C编程语言中,使用static关键字可以让全局变量和函数的作用域限定于所在文件。对于局部变量,使用static可以将其存储在静态分配的内存中而不是自动分配的内存中。虽然语言并未规定这两种内存类型的实现方式,但通常情况下,静态分配的内存会被保留在程序的数据段中,在编译时进行分配,而自动分配的内存则通常作为一个瞬时调用栈进行实现。
static
的问题会知道 linkage
意味着什么。但是,作用域的概念几乎所有语言都具备,因此任何人都应该能够根据这个描述大致理解 static
如何影响对象。出于同样的原因,它提到了“包含文件”而不是“当前编译单元”。 - natiiixstatic
并不设置作用域。即使“包含文件”也是错误的,因为作用域仅从声明符的末尾开始,而不是从文件开头开始。维基百科的条目引用如此误导,以至于会让特朗普都脸红。 - Jensregister
作为存储类别说明符(C99 6.7.1 存储类别说明符)。而且它不仅仅是一个提示,例如,无论编译器是否分配寄存器,都不能对具有存储类别register
的对象应用取地址运算符&
。 - Jens如果在函数中声明变量为静态(static),那么该变量的值将不会存储在函数调用堆栈中,当再次调用该函数时,该变量仍然可用。
如果在全局范围内声明变量为静态(static),那么其作用域将被限制在声明它的文件内。这比普通的全局变量更加安全,因为普通全局变量可以在整个程序中读取和修改。