局部变量 — 栈区
全局变量 — 静态区
栈区 — 局部变量,函数的形参
堆区 — 动态内存分配
静态区 — 全局变量,静态变量(static)
内存的使用方式:
- 新建一个变量
int a = 1;//局部变量,栈区
int global_a = 1;//全局变量,静态区
- 新建一个数组
int a[10];//与变量的内存相同,只是数组申请一块连续内存
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct S { char name[20]; int age; }; int main(void) { struct S arr[50]; return 0; }
|
C语言可以创建变长数组 — C99标准中加入了,但不一定所有编译器都支持(比如说VS)
gcc支持C99
动态内存分配
malloc和free都定义在是stdlib.h中,也可以用malloc.h中但建议用malloc.h
1. malloc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int main(void) { int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { printf("%s\n", strerror(errno)); } else { int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } } return 0; }
|
输出为0 1 2 3 4 5 6 7 8 9
C语言标准未定义size为0的情况,取决于编译器,所以应避免
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main(void) { int* p = (int*)malloc(INT_MAX); if (p == NULL) { printf("%s\n", strerror(errno)); } return 0; }
|
输出为Not enough space
2. free
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
| int main(void) { int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { printf("%s\n", strerror(errno)); } else { int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
|
如果free参数ptr是NULL,则函数什么事都不做
如果ptr指向的空间不是动态开辟的,那么free的行为是未定义的
3. calloc
开辟一块空间并把每个元素初始化为0,而malloc是随机值但每个元素是固定的随机值
1 2 3 4 5 6
| int main(void) { int* p = calloc(10, sizeof(int)); return 0; }
|
4. realloc
如果要增加空间:
若后面的空间足够增加,那么在原来的基础上增加空间,返回原地址
如果后面剩余的空间不够增加,则另外开辟一块新的空间,原空间的数据移到新空间中, 释放旧的内存空间 ,返回新空间的地址
如果增加的空间过大,超出限制导致开辟失败,返回NULL,原空间不变,不能用原空间的地址来接受,不然会丢失原空间的地址
总之不要用原来的指针来接收realloc的返回值
realloc如果参数是NULL,则和malloc功能相同
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
| int main(void) { int* p = (int*)malloc(20); if (p == NULL) { printf("%s\n", strerror(errno)); } else { for (int i = 0; i < 5; i++) { *(p + i) = i; printf("%d ", *(p + i)); } } int* ptr = realloc(p, 40); if (ptr != NULL) { p = ptr; } for (int i = 5; i < 10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
|
常见的动态内存错误
- 对NULL进行解引用操作
在开辟动态内存后一定要检查是否返回NULL,否则直接对NULL操作会出现问题
1 2 3 4 5 6 7 8 9 10 11
| int main(void) { int* p = (int*)malloc(20); for (int i = 0; i < 5; i++) { *(p + i) = i; printf("%d ", *(p + i)); } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int main(void) { int* p = (int*)malloc(20); if (p == NULL) { printf("%s\n", strerror(errno)); } else { for (int i = 0; i < 10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9
| int main(void) { int a = 10; int* p = &a; *p = 20; free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main(void) { int* p = malloc(40); if (p == NULL) { return 0; } for (int i = 0; i < 10; i++) { *p++ = i; } free(p); p = NULL; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int main(void) { int* p = malloc(40); if (p == NULL) { return 0; } free(p); free(p); return 0; }
|
解决方法:free后立即将p置为空指针,这样即使多次释放后面的释放也没有意义
1 2 3 4 5 6 7 8
| int main(void) { while(1) { malloc(1000); } return 0; }
|
练习题
1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void GetMemory(char *p) { p = (char*)malloc(100); }
void test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
int main(void) { test(); return 0; }
|
问题:
1.str以值传递给p,p只是str的一个临时拷贝,改变p不会改变str,因此str一直是NULL
2.strcopy接收到str地址是NULL,会发生程序崩溃
3.动态开辟的内存没有释放,造成内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void GetMemory(char **p) { *p = (char*)malloc(100); }
void test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; }
int main(void) { test(); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| char* GetMemory(char *p) { p = (char*)malloc(100); return p; }
void test(void) { char* str = NULL; str = GetMemory(str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; }
int main(void) { test(); return 0; }
|
2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| char* GetMemory(void) { char p[] = "hello world"; return p; }
void Test(void) { char *str = NULL; str = GetMemory(); printf(str); }
int main(void) { Test(void); return 0; }
|
返回栈空间地址的问题
返回p的地址,但里面的内容已经被销毁
3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void GetMemory(char **p, int num) { *p = malloc(num); }
void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello world"); printf(str); }
int main(void) { Test(); return 0; }
|
问题:忘记释放动态开辟的内存,导致内存泄漏
4.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void Test(void) { char *str = (char *)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } }
int main(void) { Test(); return 0; }
|
问题:free释放str指向的空间后并不会把str置为NULL,后面再调用就是非法访问
C/C++内存的开辟
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表
- 数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码
柔性数组
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct S { int n; int arr[]; };
int main(void) { struct S s; return 0; }
|
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
| struct S { int n; int arr[]; }; int main(void) { int i; struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int)); ps->n = 100; for (i = 0; i < 5; ++i) { ps->arr[i] = i; } struct S* ptr = realloc(ps, 44); if (ptr != NULL) { ps = ptr; } for (i = 5; i < 10; i++) { ps->arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } free(ps); ps = NULL; return 0; }
|
下面这种可以实现和柔性数组相同的效果
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
| struct S { int n; int* arr; }; int main(void) { struct S* ps = (struct S*)malloc(sizeof(struct S)); ps->arr = malloc(5 * sizeof(int)); int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i; } for (i = 0; i < 5; i++) { printf("%d ", ps->arr[i]); } int* ptr = realloc(ps->arr, 10 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } for (i = 5; i < 10; i++) { ps->arr[i] = i; } for (i = 5; i < 10; i++) { printf("%d ", ps->arr[i]); } free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }
|
柔性数组对比这种方案的优点:
- malloc只有一次
- 内存利用率高,内存碎片少
- 柔性数组中结构体所有成员的内存是连续的,访问效率更高