内存空间
静态内存分配 - 编译时分配
- 全局变量, 全局数组, 静态变量, 常量均存于已初始化数据段或未初始化数据段
- 局部变量, 局部数组均存于栈上; 此外, 函数调用的信息也存于栈上
示例如下:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
int global_initialized_variable = 1; // 全局变量, 存于已初始化数据段
int global_uninitialized_variable; // 全局变量, 存于未初始化数据段, 自动初始化为0
char * global_string_constant = "Coekjan";
/*
* global_string_constant 是全局变量, 存于已初始化数据段
* "Coekjan" 是只读的静态字符串, 存于已初始化数据段的常量区
*/
char global_char_array[] = "Hey!";
/*
* 该定义等价于
* char global_char_array[] = {'H', 'e', 'y', '!', '\0'};
* 实质上是定义了一个全局数组, 存于已初始化的数据段, 是可读可写的!!
* 请注意与上一个声明方法进行区分.
*/
int global_array[100]; // 全局数组, 存于未初始化的数据段
void func(char func_argument) {
/*
* func是函数, 其入口地址处于代码段
* func_argument是局部变量, 存于栈
*/
static int static_variable; // 静态变量, 存于未初始化数据段
int local_uninitialized_variable; // 局部变量, 存于栈
int local_uninitialized_array[100]; // 局部数组, 存于栈
/*
局部变量与局部数组若不进行初始化, 其值是不可预测的.
*/
putchar(func_argument);
}
int main() { // main是函数, 其入口地址处于代码段
void (*func_ptr)(char) = func;
/*
* 局部变量, 存于栈
* 把func理解为函数的入口地址, func_ptr就是指向了这个入口的函数指针
* 因此, func_ptr本身存于栈上, 而存储的值是代码段中的某处地址
*/
func_ptr('x'); // 相当于 func('x')
return 0;
}
动态内存分配 - 运行时分配
动态内存分配是在程序运行时在堆上进行的内存分配.
C程序中常常使用malloc
与calloc
进行动态内存分配. 动态内存分配有以下需要注意的地方:
动态分配的内存必须使用free
进行释放. 分析如下程序:
1
2
3
4
5
6
7
8
9
#include <stdlib.h>
int main() { // 危险行为, 请勿轻易尝试
char *ptr;
for(;;) {
ptr = (char *)malloc(sizeof(char));
*ptr = 0;
}
return 0;
}
该程序死循环中不断使用malloc
申请内存, 并写内存, 但没有使用free
函数释放这片内存, 因此运行时, 内存占用越来越高, 最终由于内存不足, 使得malloc
返回NULL
, 此时*ptr = 0;
将向0x0
地址写, 引发异常.
一般而言, 堆上分配内存(动态分配)的效率比栈上分配内存(静态分配)的效率低. 因此如果能够确定对象的最大空间需求, 静态内存分配是较好的选择. 下面的程序使用静态分配的数组模拟了链表的插入函数(结点数不大于100):
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
28
#define <stdio.h>
#define <stdlib.h>
struct node {
int data;
struct node *next;
};
struct node list[100];
struct node *head = NULL;
int list_len = 0;
struct node *insert(int data, struct node *pre) {
if (list_len >= 100) return NULL; // 内存不足
struct node *new_node = &list[list_len++]; // 在数组中"申请"内存
/*
* 动态内存分配的写法是:
* struct node *new_node = (struct node *)malloc(sizeof(struct node));
*/
new_node->data = data;
if (pre == NULL) { // 如果pre为NULL, 则新结点添加至链表头
new_node->next = head;
head = new_node;
} else { // 否则, 新结点添加至pre后
new_node->next = pre->next;
pre->next = new_node;
}
return new_node;
}
查看一个对象的地址
由于C语言提供了单目的&
运算符用于取地址, 而地址本身是一个数据, 因此可以使用printf
函数输出某对象的地址(或首地址).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
int a = 2;
int *p = &a;
printf("Value of a = %x\n", a); // 2
printf("Addr of a = %x\n", &a); // 62fe1c
printf("Value of p = %x\n", p); // 62fe1c
printf("Addr of p = %x\n", &p); // 62fe10
return 0;
}
/* 注释为本地测试结果, 不同机器的输出可能会有所不同. 一些现象的解释如下:
* 1. a与p均为局部变量, 存于栈上, 且均占4字节, 因此p的地址比a的地址低4
(栈由高地址往低地址方向延伸).
* 2. p本身存储了a的地址, 因此p的值与a的地址相同.
*/
使用宏函数方便查看对象的地址和值
你可以像如下的示例一样编写宏函数, 方便查看对象的地址和值. 下面的程序运行结果与上面的程序运行结果无区别.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#define printAddr(V) printf("Addr of " #V " = %x\n", &V)
#define printValue(V) printf("Value of" #V " = %x\n", V)
// 上述宏定义中 #V 是字符串化
int main() {
int a = 2;
int *p = &a;
printValue(a);
printAddr(a);
printValue(p);
printAddr(p);
return 0;
}
使用debug工具也可以查看对象的地址和值, 此处使用宏函数纯属是拓宽思路.