动态内存管理(2) - 详解

目录

前言

一、常⻅的动态内存的错误

介绍:

1.对NULL指针的解引⽤操作

什么是NULL指针?(讲一下)

解引用NULL指针的风险

如何避免解引用NULL指针?

2.对动态开辟空间的越界访问

越界访问的原因

越界访问的危害:

如何避免越界访问?

3.对⾮动态开辟内存使⽤free释放

如何避免此类错误?

4.使⽤free释放⼀块动态开辟内存的⼀部分

如何避免此类错误?

5.对同⼀块动态内存多次释放

如何规避重复释放?

代码分析

二、柔性数组知识

总结


前言

动态内存管理的两篇文章讲解为什么要有动态内存分配、malloc和free、calloc和realloc、常⻅的动态内存的错误、动态内存经典笔试题分析、柔性数组知识的相关内容,常⻅的动态内存的错误、动态内存经典笔试题分析、柔性数组知识为本章节知识的内容,动态内存管理知识也将讲解完成。

一、常⻅的动态内存的错误

介绍:

  在C语言动态内存管理中,错误的使用方式可能导致程序崩溃、数据损坏甚至安全漏洞。以下常见动态内存错误的解析及示例说明:

1.对NULL指针的解引⽤操作

  对NULL指针的解引用操作是C/C++中最常见的错误之一,可能导致程序崩溃或不可预期的行为,接下来先讲解对NULL指针的解引⽤操作的知识:

什么是NULL指针?(讲一下)

  • 定义NULL是一个宏常量(通常定义为(void*)0),表示指针不指向任何有效的内存地址。
  • 作用:用于初始化指针或标记指针“未使用”状态。

解引用NULL指针的风险

  1. 程序崩溃:直接访问地址0(或无效地址)会触发操作系统的内存访问错误
  2. 未定义行为:即使程序未立即崩溃,也可能出现数据损坏、逻辑错误或安全漏洞(如缓冲区溢出)。

代码例:

void test()
{
int *p = (int *)malloc(10*sizeof(int));
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

如何避免解引用NULL指针?

  • 指针使用前检查
    int* p = NULL;
    if (p != NULL) {  // 解引用前必须判断
    *p = 10;  // 安全操作
    }
  • 初始化指针:始终为指针分配有效内存后再使用(如int* p = malloc(sizeof(int));)。

1.即使逻辑上不可能为NULL,也建议检查一下。

2.注意:警告往往预示着潜在崩溃风险。

2.对动态开辟空间的越界访问

  动态内存越界访问是指程序访问了超出动态分配内存范围的空间,是C语言中危险的错误。

越界访问的原因
  1. 数组下标越界
    • malloc/calloc分配的数组(如int *p = malloc(5*sizeof(int))),访问p[5]p[-1]等超出[0, 4]范围的下标。
  2. 指针偏移错误
    • 通过指针运算(如p++)移动指针时,超出分配内存的起始/结束地址。
  3. 字符串操作不当
    • 使用strcpy/strcat等函数时,目标字符串空间不足,导致写入越界。
越界访问的危害:
  • 数据损坏:修改相邻内存区域的数据,导致程序逻辑错误。
  • 程序崩溃:访问未分配的内存区域,触发错误。

代码例:

#include
#include
int main() {
int *p = malloc(3 * sizeof(int)); // 分配3个int(12字节,假设int=4字节)
p[3] = 100; // 越界访问:合法范围是p[0]~p[2]
free(p);
return 0;
}

解释:

  • 问题p[3]超出分配的内存空间,可能覆盖其他变量。
  • 后果:后续free(p)可能崩溃。

如何避免越界访问

  • 明确内存大小:动态分配时记录内存块大小(如int size = 5; int *p = malloc(size * sizeof(int))),访问时严格检查下标是否在[0, size-1]内。
  • 使用安全函数
    • 字符串操作:strncpy(dest, src, n)(限制复制长度)。
    • 内存复制:memcpy(dest, src, n)(确保n不超过目标缓冲区大小)。

3.对⾮动态开辟内存使⽤free释放

  在C语言中,free函数的作用是释放动态分配的内存malloc/calloc/realloc的返回值)。若对非动态内存使用free,会导致严重的未定义行为,接下来,将讲解这方面知识。

注意;

以下内存不可用free释放:

  • 栈内存:函数内局部变量(如int a = 10;)、函数参数。
  • 全局/静态内存:全局变量(如int g_var;)、static变量(如static int s_var;)。
  • 常量内存:字符串常量(如char *str = "hello";中的"hello")。

错误例;

#include
#include
int main() {
int a = 10;
free(&a); // 错误:&a是栈内存地址
return 0;
}
#include
#include
int g; // 全局变量(静态存储区)
int main() {
free(&g); // 错误:全局内存无需手动释放
return 0;
}
#include
#include
int main() {
char *str = "hello"; // "hello"存储在常量区
free(str); // 错误:常量内存不可修改/释放
return 0;
}
如何避免此类错误?
  1. 明确内存来源

    • 仅对malloc/calloc/realloc返回的指针使用free,且只释放一次
    • 局部变量、全局变量、字符串常量等无需手动释放。
  2. 指针初始化与标记

    动态内存指针使用后及时free并置为NULL(如free(p); p = NULL;),避免二次释放或野指针。

4.使⽤free释放⼀块动态开辟内存的⼀部分

  先说结论:C语言的内存释放是整体释放,是从头开始释放,不可以在中间位置释放空间,即free函数只能释放整块动态内存(即malloc/calloc/realloc返回的完整内存块),禁止释放内存块的一部分(如指针偏移后的地址)。这种操作会直接导致未定义行为,是严重的内存错误。

这是常发错误:
例:

#include
#include
#include
int main() {
char *p = malloc(100); // 动态分配100字节
if (p == NULL) return 1;
char *q = p + 50; // 指针偏移,指向内存块的中间位置
free(q); // 错误:释放内存块的一部分(从第50字节开始)
return 0;
}

本质原因

动态内存分配时,系统会在内存块头部维护数据(如块大小、是否空闲、前后块指针等),用于内存管理。free函数需要通过指针找到元数据才能正确释放整块内存。若指针指向内存块中间(非起始地址),free将无法识别数据。

如何避免此类错误?
  1. 跟踪动态内存指针:始终保存malloc返回的原始指针,避免对其进行偏移或修改。
  2. 禁止对指针进行算术运算后释放:如free(p + n)free(&p[i])均为错误操作,如果要操作,可以通过中间变量来实现。

5.对同⼀块动态内存多次释放

  重复释放是错误行为,即对同一块动态内存调用多次free 是严重的内存错误,会直接导致未定义行为,释放空间free一次即可,如果多次释放同一内存,会导致程序崩溃。

#include
#include
int main() {
int *p = malloc(4); // 分配4字节内存
free(p); // 第一次释放(正确)
free(p); // 第二次释放(错误:重复释放)
return 0;
}
如何规避重复释放?

释放后立即置空指针,将指针设为NULL,后续再次free(NULL)会被系统忽略(安全操作)。

即:

#include
#include
int main()
{
int *p = malloc(4); // 分配4字节内存
free(p); // 第一次释放(正确)
p=NULL;
free(p);
return 0;
}

解:

第二次释放前已经将指针 p 显式置为 NULL,而 C 语言标准规定:free(NULL) 是一个空操作,不会对内存造成任何影响。

代码分析
  1. 第一次释放free(p) 释放了 p 指向的动态内存块,此时 p 成为野指针(指向已释放内存)。
  2. 指针置空p = NULL 将指针标记为“空指针”,避免其成为“野指针”(指向无效内存的指针)。
  3. 第二次释放free(p) 等价于 free(NULL),这是安全的,系统会忽略该操作,不会引发任何错误。

二、柔性数组知识

  也许你从来没有听说过柔性数组这个概念,但是它确实是存在的,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做柔性数组成员。

例:

struct A
{
int i;
int a[];
};

这样实现:

特点讲解:

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

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

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

#include
struct A
{
int i;
int a[];
};
int main()
{
printf("%d",sizeof(struct A));
}

为柔性数组创建空间:

#include
#include
struct A
{
int i;
int a[];
};
int main()
{
struct A* h=(struct A*)malloc(sizeof(struct A)+sizeof(int)*10);
//注意:一次性分配结构体 + 柔性数组内存(共 sizeof(struct A) + 10个int)
h->i=1;
free(h);
h=NULL;
}
  • 柔性数组的内存与结构体连续,无需单独为 a[0] 分配内存
  • 分配时需计算总大小:sizeof(struct A)(结构体本身) + sizeof(int)*10(10个int元素)。
  • 只需释放结构体指针 h,柔性数组内存会随结构体一起释放,无需单独释放 a[0]

总结

  以上就是今天要讲的内容,动态内存管理的两篇文章讲解了为什么要有动态内存分配、malloc和free、calloc和realloc、常⻅的动态内存的错误、动态内存经典笔试题分析、柔性数组知识的相关内容,希望大家能喜欢我的文章,谢谢各位。

posted @ 2025-09-25 20:53  yxysuanfa  阅读(8)  评论(0)    收藏  举报