C 语言中变量的四种存储类型

auto

The auto and register specifiers give the declared objects automatic storage class, and may be used only within functions. Such declarations also serve as definitions and cause storage to be reserved.

也就是说动态分配和释放存储空间。当我们定义一个变量时,不给它初始值,它的值是不确定的。

我们之前编写程序的时候很少显式用到 auto 定义变量。如果定义的变量前面没有加static,编译系统会默认为是 auto 的存储方式,会把变量存放在动态存储区。

auto 修饰符的定义里有这么一句「进入包含变量声明的代码时,变量开始存在。当程序离开这个代码块时,变量自动消失。它所占用的内存可被用来做别的事情」。auto 修饰的变量是存储在堆栈中的,而全局变量存储在静态存储区中,所以我们不能用 auto 修饰全局变量。

auto 存储类型说明的变量都是局部于某个程序范围内的,只能在某个程序范围内使用,通常在函数体内或函数中的复合语句里。

如,有下述定义:

1
2
auto int k ;        // 说明一个 auto 整型的 k 变量
int K ; // 省略了 auto,说明一个 auto 整型的 K 变量

这两种定义方式是相同的,省略 auto 关键字也是我们常用的方式。

注意:auto 只能用在函数体内,而不能用在全局变量中。

在 C 语言中使用 auto 定义的变量可以不予初始化但在 C++ 中必须初始化

此外,下面这种形式也是对的:

1
auto val;   // 当省略数据类型,只使用 auto 修饰变量,在 C 语言中默认变量为 int 型(函数参数类型,返回值类型等等很多默认也是 int)

register

register 称为寄存器型,使用 register 关键词修饰变量的主要目的是想将所说明的变量放入寄存器存储空间中,我们知道寄存器数量有限,且位于 CPU 的内部,这样可以加快程序的运行速度。但正因为寄存器的资源相对较少,所以编译器会判断程序所指定的需要放在寄存器中的内容有没有必要放入寄存器中去,也就是说,编译器来决定是否将指定内容放入到寄存器中。如果没有必要放入寄存器中,就使用 auto 类型作处理。综上所述,register 是一个建议性关键字,编译器可以判断出是否去执行这样一个关键字,所以这个关键字在目前的用处不大。

例:

1
2
3
register int i,sum = 0;
for (i = 0; i < 10000; i++)
sum += i;

static

这个修饰符和 auto 相对,我们不能同时用auto static int a = 10

static:称为静态存储类型,在 C 语言中,既可以在函数体内,也可在函数体外说明 static 存储类型的变量。

在函数体内说明的 static 存储类型的变量也是一种局部变量,与 auto 最大不同点是:static 存储类型的变量在内存中是以固定地址存放的,而不是以堆栈方式存放的。只要整个程序还在继续运行,静态变量就不会随着程序段的结束而消失;static 修饰的变量只被初始化一次,且变量的值有继承性。

static 的作用有三条:

  • 第一个作用:隐藏,其他文件不可见
  • 第二个作用:保持变量内容的持久
  • 第三个作用:默认初始化为 0

因此,static 的作用要区分全局(隐藏)和局部(保持持久)两种情况。

为什么是 0?

可参考:为什么在C语言中静态变量的初值是 0

  1. 不同的内存区域在程序运行时特性是不同,大致分为静态空间、栈空间、堆空间。静态空间其实是跟着程序映像一起加载到内存里的,提前就写在映像里了。无论如何都会有个初始值,反正写啥都是写,不如写 0;而栈空间和堆空间是映像加载完之后系统分配的,为了提高效率系统不会主动替你清零,C 也不会,所以未初始化的变量是内存里原有的值。简单来说,原因是效率。全局和静态变量是在编译期就确定了地址和值,并不消耗运行时的时间。局部变量是在每次函数调用时在栈上产生,你可以自行选择是否初始化。
  1. 很简单的问题,编译时没有赋值的全局变量会放在 bss 段。编译以后,bss 段是用来占位并且没有意义的。只有当程序加载运行时,虚拟存储器给 bss 段分配空间,在这段空间里请求二进制 0,程序加载时会对整个 bss 段进行清 0 操作,所以 C 语言没有赋值的全局变量是 0。

我觉得 2 比较靠谱,1 没有说到点子上。

extern

extern 称为外部参照引用型,使用 extern 修饰的变量是想引用在其它文件中或在函数体外部已经声明的变量。

如下面的程序:

1
2
3
4
5
6
int a = 1;
int main()
{
printf("a = %d\n" , a);
return 0;
}

该程序可以成功运行,输出 a 的值为 1,而如果改变一下:

1
2
3
4
5
6
int main()
{
printf("a = %d\n" , a);
return 0;
}
int a = 1;

这样在进行编译时,编译器会报错,提示变量 a 没有被定义,也就是说全局变量的作用范围是从定义处开始直至程序结束。现在,我们在该程序的基础上加上一句声明:

1
2
3
4
5
6
7
extern int a; // 注意不加类型 int 会出 warning
int main()
{
printf("a = %d\n", a);
return 0;
}
int a = 1;

注意:在编译时,即使后面没有 a 的定义,也不会报错,链接时才报错。

在这里要注意 extern 关键字对于变量的类型说明可以省略,但对于变量的类型及值是没有任何改变权限的,也就是说在上面例子中的 extern 如果写成 extern float a; 或者是 extern int a = 3; 编译时会提示错误。

extern 扩大了外部变量的作用范围。

我们知道头文件只要是存放函数和变量声明的,所以用 extern 声明的变量也尽量写在头文件中,如果写在 .cpp 或者 .c 文件中容易埋下苦果。

如何在两个文件中访问同一个全局变量

方法一:不使用头文件

  1. a.cint foo;
  2. b.cextern int foo;

方法二:使用头文件(推荐,即不在 .c.cpp 文件中使用 extern)

  1. a.hextern int foo;(注意如果不加 extern 可能会重复定义,发生冲突)
  2. a.c 中定义 int foo; 不必添加 #include "a.h"
  3. b.c 中没有定义(也不能重复定义吧),需要添加 #include "a.h"(推荐)或者在代码中添加 extern int foo;(不推荐)

全局变量与静态全局变量

全局与局部对应。与静态 static 相对的是动态 auto 变量,如函数体内用 auto 修饰的变量,动态的意思体现在函数对变量内存空间的分配和释放上,是在函数调用和结束的时候进行的。

全局变量是不用 static 修饰的全局变量。我这里也很懵逼!在 C 语言中,全局变量本身是静态存储的,为什么还叫静态全局变量?我觉得叫 static 全局变量比较好。

静态全局变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。

当程序由一个源文件构成时,全局变量与静态全局变量没有区别;当程序由多个源文件构成时,全局变量与静态静态变量不同。

静态全局变量的作用:

  1. 不必担心其它源文件使用相同变量名,彼此相互独立;
  2. 在某源文件中定义的静态全局变量不能被其他源文件使用或修改;
  3. 只能在本文件中使用。具有内部链接的静态性,不允许在其他文件里调用。

总的来说,把局部变量改变为静态变量后是改变了它的存储方式和生存期,而把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此 static 这个说明符在不同的地方所起的作用是不同的。为什么当初不设计成 file 关键字呢?表示该变量的作用域是文件。

Tips:

(下面这几句话值得记住)

  1. 若全局变量仅在单个 C 文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;

  2. 若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;

  3. 如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用 static 变量(这样的函数被称为:带「内部存储器」功能的的函数);

  4. 函数中必须要使用 static 变量的情况:当某函数的返回值为指针类型时,则必须是 static 的局部变量的地址作为返回值;若为 auto 类型,则返回为错误的指针(返回是值复制)。举个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int *foo1() {
    int a = 5;
    int *p1 = &a; // a 是局部变量,返回了 a 变量的地址
    return p1; // 所以外部再次访问的时候是随机值,但没 warning
    }

    char *foo2() {
    char *p2 = "hahah"; // 字符存储在常量区,这种情况不需要 static
    return p2; // 打印 hahah
    }

    char *foo3() {
    char p3[] = "hahahhahah"; // 字符存储在栈,并非常量
    return p3; // 产生 warning,说返回了栈地址
    }

    int main() {
    int *p = foo1();
    char *pp = foo2();
    char *ppp = foo3();

    printf("%d\n", *p);
    printf("%s\n", pp);
    printf("%s\n", ppp);

    return 0;
    }

    输出:

    1
    2
    3
    4
    5
    6
    a.c:56:12: warning: address of stack memory associated with local variable 'p3' returned [-Wreturn-stack-address]
    return p3;
    ^~
    1 warning generated.
    -2027492106
    hahah

参考

参考的链接已在对应的标题下,大部分链接忘了记录,以后要记下来。