英文原版:P413
本章主要内容:
- 17.1节介绍动态内存分配的基础知识;
- 17.2节介绍如何动态分配字符串,这要比普通的字符数组更灵活;
- 17.3节如何动态分配数组;
- 17.4节介绍当动态分配的内存块不再使用时,如何释放该内存块;
- 17.5节重点讨论链表,这是最基础的链式数据结构;
- 17.6节介绍指向指针的指针的概念;
- 17.7节介绍指向函数的指针,这是一个非常有用的概念,比如C语言中的一些功能强大的库函数都使用函数指针作为参数,比如给任意数组排序的库函数
qsort
; - 17.8节介绍受限指针特征;
- 17.9节介绍灵活的数组成员特征;
17.1节 动态存储分配
C语言的数据结构通常都是大小的固定的,比如当程序被编译后,一个数组的元素个数就是固定的。
- 由于在编写程序时需要选择这些数据结构的大小,所以固定大小的数据结构可能会出现问题;
- 如果不修改程序且重新编译程序的话,就不可能改变这些数据结构的大小。
考虑在16.3节中的inventory
程序,允许用户向数据库中添加零件。数据库是用大小为100的数组存储的。为了增大数据库的容量,我们可以增加数组的大小,重新编译该程序。但是,无论我们使数组变得有多大,总是有可能出现数组被填满。
幸运的是,C语言支持动态存储分配,即在程序执行时分配内存。
使用动态存储分配,我们可以设计按需增长和缩小的数据结构。
虽然动态存储分配可用于所有数据类型,但是动态存储分配被常用于字符串、数组、结构体等。
由于可将动态分配结构体链接起来形成链表、树及其他数据结构,所以我们重点关注动态分配结构体。
17.1.1小节 内存分配函数
为了动态分配存储,我们需要调用在<stdlib.h>
中声明的三个内存分配函数之一:
-
malloc
:分配一个内存块,但不初始化该内存块; -
calloc
:分配一个内存块,并清空该内存块; -
realloc
:重新定义之前已分配的内存块的大小;
注:
-
malloc
函数是最经常使用的; -
malloc
函数比calloc
函数更有效率; - 3个内存分配函数的返回值是
void *
类型;
当我们调用某个内存分配函数来请求一块内存时,该函数是不知道我们计划在该内存块里存储的数据类型的,所以内存分配函数不能返回一个指向普通数据类型(比如int
或者char
)的指针,而是返回一个void *
类型的值。
void *
类型
-
void *
类型的值是一个泛型指针,本质上是一个内存地址;
17.1.2小节 空指针
当调用内存分配函数时,经常有一种可能:该函数不能分配一块足够大的内存来满足我们的请求。
如果这种情形发生了,则该函数返回一个空指针。
- 空指针是一个指针,该指针不指向任何对象;
- 空指针是一个特殊的值,用于区分有效指针和无效指针;
编程提示:
- 只要函数的返回值是一个指针变量,则需要检查该返回值是否是空指针;
- 测试内存分配函数的返回值,及如果该返回值是空指针,该采取什么样的措施,都是程序员的职责;
- 尝试通过一个空指针去访问内存的效果是未定义的:程序可能崩溃或者出现不符合预期的行为;
空指针是用宏NULL
来表示的,定义在6个头文件里:<locale.h>
、<stddef.h>
、<stdio.h>
、<stdlib.h>
、<time.h>
。
程序中只要包含任何一个头文件,就能保证编译器可识别NULL
。
任何使用内存分配函数的程序都会包含头文件<stdlib.h>
,当然会使NULL
可用的。
- 任何非空指针的测试都为true;
- 只有空指针的测试为false;
例1 测试malloc
的返回值是否为NULL
p = malloc(10000);
if (p == NULL) {
/* 分配失败;应采取的措施*/
}
网友评论