Static与存储类别

为什么对Static感兴趣呢

最近在看内核代码的时候,发现其中的很多函数都使用static来声明,距离自己秋招八股文时间长了, 竟一时想不起来static的作用,急忙翻开刚买的《C prime plus》来复习复习static关键字在C语言中的作用。

存储类别、链接和内存管理

非常奇怪,我在目录中搜了好久都没找到static所属的章节,既不在变量章节介绍,也不在函数章节介绍,搜了好久, static关键字相关的介绍在第12章“存储类别、链接和内存管理”。

存储类别

左值:指定对象的表达式被称为左值。

const char *pc = "Behold a string literal!";

标识符pc是一个可以修改的左值,*pc是一个不可以修改的左值, const只能保证被PC指向的字符串内容不被修改,但是无法保证PC不指向别的字符串。

作用域

  • 块作用域: 一对花括号括起来的代码区域。
  • 函数作用域 : 仅用于goto语句的标签。这意味着一个标签首次出现在函数的内层块中,它的作用于也延伸至整个函数。
  • 函数原型作用域: 用于函数原型中的形参名(变量名)。
  • 文件作用域: 变量的定义在函数外面,具有文件作用域。 具有文件作用域的变量,从它定义处到该定义所在的文件末尾均可见。

链接

C变量有三种链接属性:

  • 外部链接:外部链接变量可以在多文件程序中使用。
  • 内部链接:具有文件作用域的变量可以是外部链接或内部链接。内部链接变量只能在一个翻译单元中使用。
  • 无链接 :具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。

如何知道文件的作用域是内部链接还是外部链接?可以查看外部定义中是否使用了存储类别说明符static

简单来说,外部链接就是通俗来说的全局变量,而内部链接则是由static关键字声明的全局变量,仅允许在一个翻译单元中使用。 而无链接就是通俗意义上的局部变量。

翻译单元: 编译器将源代码文件和所有的头文件都看成是一个包含信息的单独文件。

存储期

C变量有四种存储期:

  • 静态存储期:如果对象具有静态存储期,那么它在程序的执行期间一直存在。
  • 线程存储期: 用于并发程序设计,程序执行可以被分为多个线程。 具有线程存储期的对象,从被声明到线程结束一直存在。 以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
  • 自动存储期:块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。变长数组稍有不同,它们的存储期从声明处到块的末尾,而不是从块的开始到块的末尾。
  • 动态分配存储期:动态分配的内存在调用malloc()或相关函数时存在,在调用free()后释放。这部分的内存由程序员来管理。
存储类别 存储期 作用域 链接 声明方式
自动 自动 块内
寄存器 自动 块内,使用关键字register
静态外部链接 静态 文件 外部 所有函数外
静态内部链接 静态 文件 内部 所有函数外,使用关键字static
静态无链接 静态 块内,使用关键字static

自动变量

属于自动存储类别的变量具有自动存储期、块作用域且无链接。

关键字auto是存储类别说明符,为了有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别。 变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,变量在退出该块时变量消失。

自动变量不会初始化,除非显式初始化它。

int main(void)
{
    int repid;
    int tents = 5;

tents被初始化为5,但是repid变量是之前占用分配给repid的空间中任意的值。

寄存器变量

寄存器变量存储在寄存器而非内存中,所以无法获取寄存器变量的地址。

int main(void)
{
    register int quick;

在函数头中使用关键字register,便可以请求形参是寄存器变量。

void macho(register int n)

块作用域的静态变量

静态变量的意思是该变量在内存中原地不动,并不是说它的值不变。在块中创建具有静态存储期的变量,在程序离开它们所在的函数后,这些变量不会消失。

如果未显式初始化静态变量,它们会被初始化为0.

静态变量和外部变量在程序被载入内存时已执行完毕,不能在函数的形参中使用static。

外部链接的静态变量

外部链接的静态变量具有文件作用域、外部链接和静态存储期。

  • 如果未初始化外部变量,它们会被自动初始化为0.
  • 只能使用常量表达式初始化文件作用域变量
int tern = 1;
main()
{
    extern int tern;

第一次声明被称为定义式声明,第二次声明被称为引用式声明。不要使用extern创建外部定义,只能用它来引用现有的外部定义。

内部链接的静态变量

内部链接的静态变量具有静态存储期、文件作用域和内部链接。

int traveler = 1;
static int stayhome = 1;
int main()
{
    extern int traveler;
    extern int stayhome;

多文件

只有当程序由多个翻译单元组成时,才能体现内部链接与外部链接的区别。

C通过在一个文件中进行定义式声明,然后在其他文件中进行引用式声明来实现共享。除了一个定义式声明外,其他声明都要使用extern关键字。而且,只有定义式声明才能初始化变量。

存储类别说明符

  • auto: 表明变量是自动存储期,只用于块作用域的变量声明中。
  • register: 只用于块作用域的变量,将变量归为存储器存储类别,请求最快的速度访问变量。同时还保护了该变量的地址。
  • static: 创建的对象具有静态存储期。只要程序在运行对象就存在并保留其值,但是只有执行块内的代码时,才能通过标识符访问。
  • extern: 说明声明的变量定义在别处。

存储类别和函数

外部函数可以被其他文件的函数访问,但是静态函数只能用于其定义所在的文件。

存储类别的选择

保护性程序设计的黄金法则是:“按需知道”原则。尽量在函数内部解决该函数的任务,只共享那些需要共享的变量。