news 2026/10/5 10:27:19

动态内存管理续(c++方向必看)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动态内存管理续(c++方向必看)

引言:上一篇文章主要介绍了动态内存的函数和基本用法。https://blog.csdn.net/2301_81479880/article/details/166902312?fromshare=blogdetail&sharetype=blogdetail&sharerId=166902312&sharerefer=PC&sharesource=2301_81479880&sharefrom=from_link

本文主要是运用动态内存开辟的一些注意事项,典型错误,经典例题和柔性数组。学好动态内存管理对于后续的学习数据结构是很有帮助的。

目录

1.常见的动态内存错误

1.1对NULL指针的解引用操作

1.2对动态内存空间的越界访问

1.3对非动态开辟的内存使用free

1.4使用free释放一块动态内存开辟的一部分

1.5对同一块动态内存多次释放

1.6动态开辟内存忘记释放(内存泄漏)

2.动态内存管理经典笔试题

2.1第一题

2.2第二题

2.3第三题

3.柔性数组

3.1柔性数组的特点

3.2柔性数组的使用

3.3柔性数组的优势


1.常见的动态内存错误

1.1对NULL指针的解引用操作

void test() { int* p = (int*)malloc(INT_MAX / 4); *p = 20;//如果pd的值是NULL,就会出问题 free(p); }

使用malloc开辟内存失败时,返回值为NULL,如果不加判读直接使用,程序就会崩溃,所以在使用完malloc后及时判断,如果p不为空,程序才能继续使用。

1.2对动态内存空间的越界访问

void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { return 1; } for (i = 0; i <= 10; i++) { *(p + i) = i;//当i为10时,越界访问 } free(p); }

动态开辟的空间也是有自己使用范围的,超出这个范围就会越界访问,程序错误。

1.3对非动态开辟的内存使用free

void test() { int a = 10;///局部变量创建在栈区上 int* p = &a; free(p); }

如果我们不信邪头铁尝试一下,程序直接就崩溃了,我们上一篇文章讲了,free函数用来释放动态开辟的内存,但是局部变量int是创建在栈区,强行使用free释放程序就会崩溃。

1.4使用free释放一块动态内存开辟的一部分

int main() { int* p = (int*)calloc(5, sizeof(int)); if (p == NULL)//开辟失败时返回NULL时 { perror("use malloc");//打印错误信息 return 1; } //使用空间 int i = 0; for (i = 0; i < 5; i++) { *p = i + 1; p++; } //释放内存 free(p);//这里的p不再是起始位置 p = NULL; return 0; }

程序执行完后p指向的是下标4,或者是指向中间位置,这个时候使用free5个字节,程序也会崩溃,所以释放空间时,一定要让p指向起始位置。

1.5对同一块动态内存多次释放

void test() { int* p = (int*)malloc(100); free(p); //解决办法: p = NULL把p置为空 free(p);//重复释放 }

同一块空间只能释放一次吗,重复释放程序就会报错,如果我们养好良好的代码习惯,每次释放完空间后,把p置NULL,后面的free就不会生效了。

1.6动态开辟内存忘记释放(内存泄漏)

void test() { int* p = (int*)malloc(100);//开辟了100字节的空间 if (NULL != p) { *p = 20; } } int main() { test(); while (1);//死循环 return 0; }

当动态开辟的空间忘记释放时,别人是无法使用这块空间的,这就造成了内存泄露

注意:动态开辟的空间一定要以正确的方法释放,如果不释放也要交代清楚,你在代码中进行了动态内存分配,记得释放。

小结:

1.动态申请的内存空间,可以使用free直接释放。

2.如果没有使用free释放,在程序结束时,这个内存也会被操作系统回收。

2.动态内存管理经典笔试题

2.1第一题

#include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }

这个代码能运行吗?

我们可以看到,程序的返回值不是0,说明程序在运行中就崩溃了。

解析:

C 语言只有值传递。调用GetMemory(str)时,传给形参p的是str的一份拷贝,p和str是两个不同的变量。

所以strcpy(NULL, "hello world")就是往地址 0 写数据,属于未定义行为,通常表现为段错误。

顺带一提,那块 100 字节的内存在函数返回后就没人持有它的地址了,同时造成内存泄漏。

(注意:"hello world"连结尾\0共 12 字节,100 字节本身够用,这里不是越界问题,而是指针根本没被赋值。)

2.2第二题

char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }

这个代码正确吗,运行结果是什么?

解析:

创建char*类型的str并初始化为NULL,GetMemory函数中返回了p的地址(假设为0x12ff48),由str接收,现在str中存放的就是0x12ff48,可以通过地址打印数组hello world。

看起来没有问题,但是数组p是局部变量,它的生命周期只在GetMemory函数中,出了函数它就被释放掉了,所以str接收的其实是一个已经释放掉的空间,此时它就成为了野指针,强行打印就是非法访问,结果是一堆乱码。

2.3第三题

#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了之后再次使用动态开辟的空间,ree(str)释放的是那块堆内存,str这个变量本身不受任何影响,它依然保存着原来的地址,所以if (str != NULL)永远为真,这个判断毫无意义,反而给了人一种"已经检查过、很安全"的错觉。

随后往已经回收的空间内写数据,如果这块空间已经分配出去了,会破坏别人的数据,但也有可能还没有分配出去,程序正常跑完。

当我们free完成后,将str置为NULL,程序就没有问题了。

3.柔性数组

在c99中,结构体的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

struct st_type { int i; int a[]; //int a[];有些编译器不支持这种写法 }

3.1柔性数组的特点

1.结构中的柔性数组成员前面必须至少⼀个其他成员。

2.sizeof返回的这种结构大小不包括柔性数组的内存。

3. 包含柔性数组成员的结构⽤malloc()函数进性内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

3.2柔性数组的使用

struct S { int n; int arr[];//柔性数组成员,我希望arr开始的时候能存放5个整行,后期arr的空间大小可调整 }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));//24 //创建一个结构体指针来接收 if (ps == NULL) { perror("use malloc"); return 1; } ps->n = 100; int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i+1; } //扩容 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 10 * sizeof(int)); if (ptr != NULL) { //扩容失败 perror("use realloc"); } else { //扩容成功 ps = ptr; ptr = NULL; for (i = 5; i < 10; i++) { ps->arr[i] = i + 1; } } //释放 free(ps); ps = NULL; return 0; }

当我们的空间大小需要调整时,通过柔性数组就可以做到。

3.3柔性数组的优势

struct S { int n; int *arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { perror("use malloc"); return 1; } ps->n = 100; int* ptr = (int*)malloc(5 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } else { perror("use malloc"); return 1; } int i = 0; for (i = 0; i < 5; i++) { ps->arr[i] = i+1; } //扩容 struct S* ptr2 = (struct S*)realloc(ps->arr, 10 * sizeof(int)); if (ptr2 != NULL) { //扩容失败 perror("use realloc"); } else { //扩容成功 ps->arr = ptr2; ptr2 = NULL; for (i = 5; i < 10; i++) { ps->arr[i] = i + 1; } } //释放 free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }

这里使用结构体包含指针的方式也能完成,但是柔性数组有两个优势。

1.方便内存释放:

如果我们的代码是在一个给别人用的函数中,你进行了二次分配内存,并且把整个结构体返还给用户,用户可以通过free释放结构体,但是如果用户不知道结构体内部的成员也要free,就会造成内存泄漏。所以我们把结构体的内存和成员变量要的内存一次性分配好,然后返回一个结构体指针,这样只要做一次free就可以把所有的内存释放。

2.有利于提高访问速度

连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 10:26:09

Python `import win32api` 技术报告:Windows 系统级自动化实践

import win32api 通常意味着项目正在使用 pywin32 库中的 win32api 模块。pywin32 是 Python 访问 Windows API 的核心扩展库&#xff0c;提供对系统调用、窗口消息、注册表、文件操作、进程管理以及 COM 自动化的支持。 在 Python 技术报告中&#xff0c;win32api 的价值在于&…

作者头像 李华
网站建设 2026/10/5 10:25:31

(154页PPT)行业知识培训某省市环境卫生(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 &#xff08;154页PPT&#xff09;行业知识培训某省市环境卫生.pptx_基于物联网的停车场管理方案资源-CSDN下载 资料解读&#xff1a;行业知识培训某省市环境卫生 详细资料请看本解读文章的最后…

作者头像 李华
网站建设 2026/10/5 10:25:22

ARM裸机开发-SPI

目录 一.背景 SPI四种工作模式 三种协议对比 ADXL345传感器 二.寄存器 con config start 三.实现 1.初始化 2.读写 原理 ADC调用 一.背景 SPI 是英语 Serial Peripheral interface(串行外设接口) 的缩写&#xff0c;顾名思义就是串行外围设备接口。是 Motorola首先…

作者头像 李华