用malloc()申请存储器……

申请存储器的函数叫malloc(),是memory allocation(存储器分配)的意思。malloc()接收一个参数:所需要的字节数。通常你不知道确切的字节数,因此malloc()经常与sizeof运算符一起使用,像这样:

用malloc()申请存储器…… - 图1

sizeof告知某种数据类型在系统中占了多少字节。这种数据类型可以是结构,也可以是intdouble这样的基本数据类型。

malloc()函数为你分配一块存储器,然后返回一个指针,指针中保存了存储器块的起始地址。那么这个指针是什么类型呢?事实上,malloc()返回的是通用指针,即void*类型的指针。

用malloc()申请存储器…… - 图2

……用free()释放存储器

一旦在堆上创建了存储器,就可以随时使用它。一旦完成了工作,就需要用free()函数释放存储器。

free()需要接收malloc()创建的存储器的地址。只要告诉C标准库存储器块从哪里开始,它就能查阅记录,知道要释放多少存储器。假如想要释放上面那行代码分配的存储器,可以这样做:

用malloc()申请存储器…… - 图3

好了,现在我们对动态存储器有了更深的了解,可以开始写代码了。

用malloc()申请存储器…… - 图4

不好!兼职演员来了……

这群有抱负的演员又有一部戏杀青了,所以他们有时间来帮你写代码。他们写了一个实用函数,函数根据你传给它的名字创建新的island结构:

用malloc()申请存储器…… - 图5

这个函数看起来很酷。演员们发现岛上大部分机场的开关门时间相同,所以他们把openclose字段设为了默认值,函数返回一个指针,指向新创建结构。

用malloc()申请存储器…… - 图6

用malloc()申请存储器…… - 图7脑力风暴

仔细观察create()函数的代码,你认为会有问题吗?好好想一想,然后再翻页。

用malloc()申请存储器…… - 图8

消失的岛屿案件

航空日志:11:00,周五,晴。我们编写了create()函数,它动态分配存储器。软件组的人说它可以用于试飞。

  1. island* create(char *name)
  2. {
  3. island *i = malloc(sizeof(island));
  4. i->name = name;
  5. i->opens = "09:00";
  6. i->closes = "17:00";
  7. i->next = NULL;
  8. return i;
  9. }

14:15,多云,百慕大附近,方向西北,每小时15海里,逆风飞行。我们在第一站降落,软件组提供了基本的代码,我们在命令行中 入了岛名。

用malloc()申请存储器…… - 图9

14:45,发生地震,飞机在起飞时发生了轻微的摇晃。软件组在飞机上待命,可乐供给不足。

15:35,到达第二座岛,天气晴朗,无风。在程序中输入信息。

用malloc()申请存储器…… - 图10

17:50,返回总部,整理日志。奇怪的事发生了,测试程序生成的飞行日志似乎出了错。程序记录了今日的行程,但第一座岛的名字莫名其妙地修改了,赶快让软件组来调查此事。

用malloc()申请存储器…… - 图11

第一座岛的名字怎么了?create()函数出错了吗?你能从调用函数的代码中看出端倪吗?

用malloc()申请存储器…… - 图12

消失的岛屿案件

第一座岛的名字怎么了?

再看一遍create()函数:

  1. island* create(char *name)
  2. {
  3. island *i = malloc(sizeof(island));
  4. i->name = name;
  5. i->opens = "09:00";
  6. i->closes = "17:00";
  7. i->next = NULL;
  8. return i;
  9. }

当代码记录岛名时,并没有接收一份完整的name字符串,而只是记录了name字符串在存储器中的地址。这有关系吗?name字符串在哪里?为了回答这两个问题,我们看一眼调用create()函数的代码。

  1. char name[80];
  2. fgets(name, 80, stdin);
  3. island *p_island0 = create(name);
  4. fgets(name, 80, stdin);
  5. island *p_island1 = create(name);

程序要求用户输入每座岛的名字,但两次它都使用本地的字符数组name来保存岛名,也就是说两座岛共享同一个name字符串,一旦局部变量name更新为第二座岛的名字,第一座岛的名字也就改变了。

用malloc()申请存储器…… - 图13聚焦字符串复制

在C语言中,经常需要复制字符串。你可以调用malloc()函数在堆上创建一些空间,然后手动把字符串的所有字符复制到堆上。但你猜怎么着?其他程序员早就想到了,他们在string.h头文件中创建了一个叫strdup()的函数。

假设你有一个指向你想复制的字符串常量的指针:

  1. char s = "MONA LISA";

用malloc()申请存储器…… - 图14

strdup()函数可以把字符串复制到堆上:

  1. char copy = strdup(s);
  1. strdup()函数计算出字符串的长度,然后调用malloc()函数在堆上分配相应的空间。

    用malloc()申请存储器…… - 图15

  2. 然后strdup()函数把所有字符复制到堆上的新空间。

    用malloc()申请存储器…… - 图16

也就是说,strdup()总是在堆上创建空间,而不是在栈上,因为栈用来保存局部变量,而局部变量很快就会被清除。

因为strdup()把新字符串放在堆上,所以千万记得要用free()函数释放空间。