到目前为止数组的大小都是写死、编译期就知道的。但很多程序运行前根本不知道要多少空间,用户可能会输入任意长度的数据。动态内存分配(dynamic allocation)就是答案:运行时按需向系统申请内存,用完再归还。
为什么需要动态分配
固定数组的两个尴尬:空间常年不够用或太浪费。而动态分配让你:
- 要多大给多大,不写死在代码里。
- 不用的内存调用
free()随时释放。 - 也因此能搭出链表这类边吃边长的数据结构。
malloc 分配 + free 释放
从头到尾走一遍。假设有个 person 结构体:
typedef struct {
char *name;
int age;
} person;给它动态开一块内存:
#include <stdlib.h>
person *myperson = (person *)malloc(sizeof(person));拆开讲:
malloc(sizeof(person)),向系统申请"刚好塞下一个 person"的内存,返回指向这块区域的指针。(person *),强制类型转换。malloc 返回的是void *(无类型指针),转成person *才好用来给 myperson。其实 C 语言会自动帮你转,不写也能过,写上更明确。sizeof在这里不是真正调用的函数,编译器直接把它换算成 person 结构体占的字节数。
用起来和普通结构体指针一样,用 ->:
myperson->name = "John";
myperson->age = 27;
printf("%s,%d 岁\n", myperson->name, myperson->age);用完必须释放:
free(myperson);⚠️ 一句话警钟:free 释放的是指针指向的这块内存,不是指针变量本身。free 之后,myperson 还指着那块已经归还的区域,这时的指针就叫悬垂指针,不能再碰。更稳健的处理方式是紧接着 myperson = NULL; 断干净。
malloc 可能失败
内存耗尽时 malloc 返回 NULL。好习惯是判一下再干活:
person *p = (person *)malloc(sizeof(person));
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}动态分配数组:大小随便定
给数组动态开内存,元素数可以是运行时的变量:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
char *pvowels = (char *)malloc(n * sizeof(char));
pvowels[0] = 'A';
pvowels[1] = 'E';
*(pvowels + 2) = 'I'; // 可以看到,下标和解引用混着用都行
pvowels[3] = 'O';
*(pvowels + 4) = 'U';
for (int i = 0; i < n; i++) {
printf("%c ", pvowels[i]);
}
printf("\n");
free(pvowels); // 用完归还
return 0;
}注意 pvowels 本质是指针,只是能当下标使,再次印证指针和数组用起来像,地位不同。
二维的动态分配:指针的指针
二维数组动态分配得用指向指针的指针:先给行指针数组开内存,再给每一行开内存。
#include <stdio.h>
#include <stdlib.h>
int main() {
int nrows = 2, ncols = 5;
char **pvowels = (char **)malloc(nrows * sizeof(char *));
for (int i = 0; i < nrows; i++) {
pvowels[i] = (char *)malloc(ncols * sizeof(char));
}
pvowels[0][0] = 'A'; pvowels[0][1] = 'E'; pvowels[0][2] = 'I';
pvowels[0][3] = 'O'; pvowels[0][4] = 'U';
pvowels[1][0] = 'a'; pvowels[1][1] = 'e'; pvowels[1][2] = 'i';
pvowels[1][3] = 'o'; pvowels[1][4] = 'u';
for (int i = 0; i < nrows; i++) {
for (int j = 0; j < ncols; j++) {
printf("%c ", pvowels[i][j]);
}
printf("\n");
}
/* 释放顺序:先每行,再顶层 */
for (int i = 0; i < nrows; i++) {
free(pvowels[i]);
}
free(pvowels);
return 0;
}释放的顺序必须和分配相反:先 free 掉每一行,再 free 那一组行指针。顺序反了会漏内存。
内存泄漏:用多少还多少
每次 malloc 出来的内存都要用 free 还回去,忘了还就泄漏,程序一直跑,内存越占越多,某天直接崩。经验:谁申请,谁释放;尽早释放,绝不二次释放。 动态分配是 C 语言里最容易出事故的地方,下一篇的杨辉三角练习正好把它练熟。