2.2 C 的内存的使用方法
2.2.1 C的变量的种类
C 语言的变量具有区间性的作用域*。
* 在标准中,“作用域”( scope )和 “连接”(linkage)是分别定义的,用语句块包围的是作用域,static 和 extern 分别控制静态 连接和外部连接。对于全局变量,作用域指文件作用域,链接指外部链接。对于程序员来说,这些方式都是控制命名空间的,它们没有什么不同。在本书中,我们统一使用“作用域”这种叫法。
在开发一些小程序的时候,也许我们并不在意作用域的必要性。可是,当你书写几万行,甚至几十万行的代码的时候,没有作用域肯定是不能忍受的。
C 语言有如下三种作用域。
- 全局变量
在函数之外声明的变量,默认地会成为全局变量。全局变量在任何地方都是可见的。当程序被分割为多个源代码文件进行编译时,声明为全局变量的变量也是可以从其他源代码文件中引用的。
- 文件内部的静态变量
就算对于像全局变量那样被定义在函数外面的变量,一旦添加了 static
,作用域就只限定在当前所在的源代码文件中。通过 static
指定的变量(包括函数),对于其他源代码文件是不可见的。在英语中,static
是“静态的”的意思,我实在想不明白为什么这个功能莫名其妙地被冠以“static”,这一点可以算是 C 语言的一个未解之谜。
- 局部变量
局部变量是指在函数中声明的变量。局部变量只能在包含它的声明的语句块(使用{}
括起来的范围)中被引用。
局部变量通常在函数的开头部分进行声明,但也可以在函数内部某语句块的开头进行声明。例如,在“交换两个变量的内容时,需要使用一下临时变量”的情况下,将局部变量声明放在当前语句块开头还是比较方便的。
局部变量通常在它所在的语句块结束的时候被释放。如果你不想释放某个局部变量,可以在局部变量上加上 static
进行声明(在后面有详细说明)。
另外,除了作用域不同,C 的变量之间还有存储期(storage duration)的 差别。
- 静态存储期(static storage duration)
全局变量、文件内的 static
变量、指定 static
的局部变量都持有静态存储期。这些变量被统称为静态变量。
持有静态存储期的变量的寿命从程序运行时开始,到程序关闭时结束。换句话说,静态变量一直存在于内存的同一个地址上。
- 自动存储期(auto storage duration)
没有指定 static
的局部变量,持有自动存储期。这样的变量被称为自动变量。
持有自动存储期的变量,在程序运行进入它所在的语句块时被分配以内存区域,该语句块执行结束后这片内存区域被释放*。
- 如果说明得细致一些,在几乎所有的处理环境中,并不是“程序执行进入语句块时”给自动变量分配内存区域,而是在“程序执行进入函数时”统一地进行内存区域分配的。
这个特征通常使用“栈”的机制来实现。2.5 节中会对此做详细说明。
接下来就不是“变量”了。C 中可以使用 malloc()
函数动态分配内存。通过 malloc()
动态分配的内存,寿命一直延续到使用 free()
释放它为止。
在程序中,如果需要保持一些数据,必须在内存中的某个场所取得相应大小的内存区域。总结一下,在 C 中有三种内存区域的寿命。
- 静态变量
寿命从程序运行时开始,到程序关闭时结束。
- 自动变量
寿命到声明该变量的语句块被执行结束为止。
- 通过
malloc()
分配的领域
寿命到调用 free()
为止。
要 点
C 中有三种内存领域的寿命。
静态变量的寿命从程序运行时开始,到程序关闭时结束。
自动变量的寿命到声明该变量的语句块执行结束为止。
通过
malloc()
分配的领域的寿命到调用free()
为止。
补充 存储类型修饰符
在 C 的语法中,以下关键字被定义为“存储类型修饰符”。
- typedef extern static auto register
可是,在这些关键字中,真正是“指定存储区间”的关键字,只有
static
。可是,当你在函数的外面使用
static
的时 候,就是使用作用域来控制了,而不是使用存储期。
extern
使得在其他地方定义的外部变量可以在本地可见;auto
是默认的,所以没有显式指定的必要;register
可以给出编译器优化提示(如今的编译已经很先进了,所以一般也不会使用这个关键字);至于typedef
,它只是因为可以给编码带来便利才被归纳到存储类型修饰符中来的。希望大家不要被这众多的“存储类型修饰符”搞得手忙脚乱。
2.2.2 输出地址
正如之前所说,C 的变量中有几个阶段的作用域,而且变量之间还有“存储期”的区别。此外,通过 malloc()
可以动态分配内存。
在内存中,这些变量究竟是怎样配置的呢?不如让我们来写个测试程序验证一下(参照代码清单 2-2)。
代码清单 2-2 print_address.c
1: #include <stdio.h>
2: #include <stdlib.h>
3:
4: int global_variable;
5: static int file_static_variable;
6:
7: void func1(void)
8: {
9: int func1_variable;
10: static int func1_static_variable;
11:
12: printf("&func1_variable..%p\n", &func1_variable);
13: printf("&func1_static_variable..%p\n", &func1_static_var iable);
14: }
15:
16: void func2(void)
17: {
18: int func2_variable;
19:
20: printf("&func2_variable..%p\n", &func2_variable);
21: }
22:
23: int main(void)
24: {
25: int *p;
26:
27: /*输出指向函数的指针*/
28: printf("&func1..%p\n", func1);
29: printf("&func2..%p\n", func2);
30:
31: /*输出字符串常量的地址*/
32: printf("string literal..%p\n", "abc");
33:
34: /*输出全局变量*/
35: printf("&global_variable..%p\n", &global_variable);
36:
37: /*输出文件内的static 变量的地址*/
38: printf("&file_static_variable..%p\n", &file_static_var iable);
39:
40: /*输出局部变量*/
41: func1();
42: func2();
43:
44: /*通过malloc 申请的内存区域的地址*/
45: p = malloc(sizeof(int));
46: printf("malloc address..%p\n", p);
47:
48: return 0;
49: }
在我的环境中运行结果如下:
&func1..0x8048414
&func2..0x8048440
string literal..0x8048551
&global_variable..0x804965c
&file_static_variable..0x8049654
&func1_variable..0xbfbfd9d8
&func1_static_variable..0x8049650
&func2_variable..0xbfbfd9d8
malloc address..0x805b030
一开始我们说要将变量的地址输出,代码清单 2-2 的第 28~29 行却输出了指向函数的指针。
尽管到目前为止没有提到过函数指针的问题,但是这次的代码中出现了函数指针。函数通过编译器解释成机器码,并且被配置在内存的某个地方的地址上。
在 C 中,正如数组在表达式中可以被解读成指针一样,“函数”也同时意味着“指向函数的指针”。通常,这个指针指向函数的初始地址。
第 32 行输出使用""
包围的字符串(字符串常量)的地址。
在 C 中,“字符串”是作为“char
的数组”来表现的。字符串常量类型也是“char
的数组”,因为表达式中的数组可以解读成“指向初始元素的指针”,所以表达式中的“abc
”,同样也意味着保存这个字符串内存区域的初始地址。
字符串常量在 C 中也被做了特别对待,它总让人感觉“不知道它被保存在内存的哪一片区域”,所以在这里我们也尝试输出它的地址。
第 35 行和第 38 行,分别输出了全局变量的地址和文件内 static
变量的地址。
第 41 行和第 42 行,调用了函数 func1()
和 func2()
。第 12 行和第 20 行输出自动变量的地址,第 13 行输出 static
局部变量的地址。
再回到 main()
函数,在第 46 行输出利用 malloc()
分配的内存区域的地址。
接下来,让我们来观察一下实际被输出的地址。
乍一看,都是 0x80……、0xbf……这样的地址,有点晕。
将地址按照顺序重新排列,并且整理成表 2-1。
表2-1 地址一览表
地 址 | 内 容 |
---|---|
0x8048414 | 函数func1() 的地址 |
0x8048440 | 函数func2() 的地址 |
0x8048551 | 字符串常量 |
0x8049650 | 函数内的static 变量 |
0x8049654 | 文件内static 变量 |
0x804965c | 全局变量 |
0x805b030 | 利用malloc() 分配的内存区域 |
0xbfbfd9d8 | func1() 中的自动变量 |
0xbfbfd9d8 | func2() 中的自动变量 |
通过观察,我们发现“指向函数的指针”和“字符串常量”被配置在非常近的内存区域。此外,函数内 static
变量、文件内 static
变量、全局变量等这些静态变量,也是被配置在非常近的内存区域。接下来就是 malloc()
分配的内存区域,它看上去和自动变量的区域离得很远。最后你可以发现,func1()
和 func2()
的自动变量被分配了完全相同的内存地址。
如果使用图来说明,应该是下面这样的感觉(参照图 2-3)。
图 2-3 各种各样的地址
在后面的章节中,我们将会逐一对这些内存区域进行详细的说明。