C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(上):https://developer.aliyun.com/article/1513201
3. 常见的动态内存错误
3.1 对空指针的解引用操作
#include <stdlib.h> #include <stdio.h> int main() { int* p = (int*)malloc(9999999999);//开辟失败,返回空指针 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; // 对空指针进行解引用操作,非法访问内存 } return 0; } //解决方案:对 malloc 函数的返回值做判空处理 #include <stdlib.h> #include <stdio.h> int main() { int* p = (int*)malloc(9999999999); // 对malloc函数的返回值做判空处理 if (p == NULL) { perror("main") return 1; } int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } return 0; }
记得对 malloc 函数的返回值做判空处理
3.2 对动态开辟空间的越界访问
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(10 * sizeof(int)); // 申请10个整型的空间 if (p == NULL) { perror("main"); return 1; } int i = 0; // 越界访问 - 指针p只管理10个整型的空间,根本无法访问40个 for (i = 0; i < 40; i++) { *(p + i) = i; } free(p); p = NULL; return 0; }
为了防止越界访问,使用空间时一定要注意开辟的空间大小。
3.3 对非动态开辟内存使用free释放
#include <stdio.h> #include <stdlib.h> int main() { int arr[10] = { 0 }; // 在栈区上开辟 int* p = arr; // 使用 略 free(p); // 使用free释放非动态开辟的空间 p = NULL; return 0; }
不要对非动态开辟的内存使用 free,否则会出现难以意料的错误。(程序可能会卡死)
3.4 使用free释放一块动态开辟内存的一部分
#include <stdio.h> #include <stdlib.h> int main() { int* p = malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; for (i = 0; i < 5; i++) { *p++ = i; // p指向的空间被改变了 //而且存在内存泄露风险 } free(p);//error p不再指向动态内存的起始位置 p = NULL; return 0; }
注意事项:这么写代码会导致 p 只释放了后面的空间。没人记得这块空间的起始位置,
再也没有人找得到它了,这是很件很可怕的事情,会存在内存泄露的风险。
释放内存空间的时候一定要从头开始释放。
3.5 对同一块动态内存多次释放
#include <stdio.h> #include <stdlib.h> int main() { int* p = malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; for (i = 0; i < 10; i++) { p[i] = i; } // 释放 free(p); // 一时脑热或者出了函数后忘了,再一次释放(程序可能会卡死) free(p);//error return 0; }
解决方案:在第一次释放后紧接着将 p 置为空指针,free对空指针释放什么都不会做
3.6 动态开辟内存忘记释放(内存泄漏)
#include <stdio.h> #include <stdlib.h> void test() { int* p = (int*)malloc(100); if (p == NULL) { return; } // 使用 略 // 此时忘记释放了 } int main() { test(); free(p); // 此时释放不了了,没人知道这块空间的起始位置在哪了 p = NULL; }
malloc 这一系列函数 和 free 一定要成对使用,记得及时释放。
你自己申请的空间,用完之后不打算给别人用,就自己释放掉即可。
如果你申请的空间,想传给别人使用,传给别人时一定要提醒别人用完之后记得释放。
动态开辟的内存空间有两种回收方式: 1. 主动释放(free) 2. 程序结束
如果这块程序在服务器上 7x24 小时运行,如果你不主动释放或者你找不到这块空间了,
最后就会导致内存泄漏问题。内存泄漏(Memory Leak)是指程序中已动态分配的堆内存
由于某种原因程序未释放或无法释放,造成系统内存的浪费,
导致程序运行速度减慢甚至系统崩溃等严重后果。
4. 几道经典的笔试题
题目选自高质量的C++/C编程指南、Nice2016校招笔试题。(可以当做对应练习使用)
(所以这篇在最后只留了没那么有关的几道编程题,)
4.1 题目1:
下列代码存在什么问题?请指出问题并做出相应的修改。
#include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory(char* p) { p = (char*)malloc(100); } void Test() { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }
解析:(笔试和面试时要能把问题用语言说出来)
str 传给 GetMemory 函数时为值传递,所以 GetMemory 函数的形参 p 是 str 的一份临时拷贝。
在 GetMemory 函数内部动态开辟的内存空间的地址存放在了 p 中,并不会影响 str。
所以当 GetMemory 函数返回之后, str 仍然是 NULL,导致 strcpy 拷贝失败。
其次,随着 GetMemory 函数的返回,形参 p 随即销毁并且没有及时的使用 free 释放
从而导致动态开辟的100个字节存在内存泄露问题。根据经验,程序会出现卡死的问题。
代码改法1:
#include <stdio.h> #include <stdlib.h> #include <string.h> // ↓ 修改返回类型为char* char* GetMemory(char* p) { p = (char*)malloc(100); return p; // 将p带回来 } void Test() { char* str = NULL; str = GetMemory(str); // 用str接收,此时str指向刚才开辟的空间 strcpy(str, "hello world"); // 此时copy就没有问题了 printf(str);//printf("hello world");传给printf函数的也是h的地址,所以没问题 // 用完之后记得free,就可以解决内存泄露问题 free(str); str = NULL; // 还要将str置为空指针 } int main() { Test(); return 0; }
代码改法2:
#include <stdio.h> #include <stdlib.h> #include <string.h> // ↓ 用char**接收 void GetMemory(char** p) { *p = (char*)malloc(100); } void Test() { char* str = NULL; GetMemory(&str); // 址传递,就可以得到地址 strcpy(str, "hello world"); printf(str); // 记得free,就可以解决内存泄露问题 free(str); str = NULL; // 还要将str置为空指针 } int main() { Test(); return 0; }
4.2 题目2:
请问运行Test 函数会有什么样的结果?
#include <stdio.h> #include <stdlib.h> char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
解析:
GetMemory 函数内部创建的数组实在栈区上创建的,出了函数 p 数组的空间就还给了操作系统,
返回的地址是没有实际意义的,如果通过返回的地址去访问内存,就会导致非法访问内存问题。
4.3 题目3:
请问运行Test 函数会有什么样的结果?
#include <stdio.h> #include <stdlib.h> void GetMemory(char** p, int num) { *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } int main() { Test(); return 0; }
解析:
就是对第一题的修改,但是没有 free和置空,导致内存泄露。
4.4 题目4:
请问运行Test 函数会有什么样的结果?
#include <stdio.h> #include <stdlib.h> #include <string.h> void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; }
解析:
free 之后没有将 str 置为空指针,
导致 if 为真,对已经释放掉的内存进行了访问,引发非法访问的问题。
改法:free 后将 str 置为空指针
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(下):https://developer.aliyun.com/article/1513211