动态内存管理及防御性编程

2023-05-16

概述:C语言的优势是可以直接访问内存地址,也就是指针操作,但其缺陷也是因为直接内存访问。如何通过防御性编程提前发现问题,尽可能减少内存异常产生的后果,就是本文的重点。

1、内存划分

一般内存区域划分五段:

**栈区(stack)**有时也称为堆栈,重点在栈字,存放函数内部临时变量

**堆区(heap)**也就是动态申请(malloc)、释放(free)的内存区域

**数据区(data)**初始化的全局变量和静态变量, 占用可执行文件空间;rodata 固定不变const修饰的全局变量,不占内存空间

bss区未初始化的全局变量、静态变量(static关键字描述的),初始化为全0的全局变量,不占用可执行文件大小

**代码区(text)**程序二进制文件

最终下载的可执行文件包括代码(text)和数据(data)。内存的分配一般如下图:图片其中堆和栈的地址分配方向相反,栈比较特殊,下面微信公众号【嵌入式系统】以栈空间异常使用为例:

#include <stdio.h>

int main(void)
{
    int a=100;
    int b[3]={0};
    int c=200;

    printf("ori> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);

    printf("   > b[%p]\r\n",&b);
    b[0]=0;
    b[1]=1;
    b[2]=2;
    b[3]=3;//error ->a

    printf("new> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);

    return 0;
}

运行结果:

ori> a[0028FEBC]=100,c[0028FEAC]=200
   > b[0028FEB0]
new> a[0028FEBC]=3,c[0028FEAC]=200

结合打印的变量地址,栈空间分配如下图,因为数组b的操作越界,导致了变量a的值被覆盖。图片针对个人情况,一般情况下内存溢出都是使用数组越界,所以在异常值后或者前查看有没数组(全局变量可以查map文件),检查数组的操作是否正确。

除了堆区,其他几个区都是有编译器和系统运行时自动处理的,而堆区由开发者来操作的。这既是便利,也是隐患,一旦操作失误就是内存泄漏或溢出。

2、动态内存管理

在硬件资源固定的情况下,栈和堆的空间此消彼长,合理的定义堆的空间,为不同任务分配合适的栈空间也是至关重要的。以FreeRTOS内核代码为例,《FreeRTOS及其应用》分别解读其5种动态内存,也就是堆的分配方式,其他系统的原理差不多。参考Guide文档 https://www.freertos.org/Documentation/RTOS_book.html Guide

FreeRTOS 内核提供了 5 种内存管理算法,源文件在Source\portable\MemMang 下,使用时选择其中一个。

heap_1.c内存管理方案简单,它只能申请内存而不能进行内存释放。图片一些低端嵌入式系统并不会经常动态申请与释放内存,在系统启动后申请,一直使用下去,永不释放,适合这种方式,也可近似理解为多个全局小数组合并的使用。

heap_2.c 方案支持申请和释放,但是它不能把相邻的两个小的内存块合成一个大的内存块, 随着不断的申请释放,空闲空间会分割为很多小片段,如下图图片持续申请、释放一定次数,就会出现剩余空间的和较大,但却申请不到内存的情况,如上图剩余空间是900,但无法申请600,因为没有连续的600空间。如果每次申请内存大小都是固定的,就不存在内存碎片问题,但实际不会这样,因此不推荐。

heap_3.c 方案只是封装了标准 C 库中的 malloc()和 free()函数,由编译器提供,需要通过编译器或者启动文件设置堆空间,封装是为了保证线程安全。

heap_4.c 方案是在heap_2.c 基础上,对内存碎片进行了改进。图片如图E到F,用户释放后,把相邻的空闲的内存块合并成一个更大的块,这样可以减少内存碎片。

heap_5.c 方案在实现动态内存分配时与 heap4.c 方案一样,采用最佳匹配算法和合并算法,并且允许内存堆跨越多个非连续的内存区,也就是允许在不连续的内存堆中实现内存分配,比如做图形显示,可能芯片内部的 RAM 不足,额外扩展SDRAM,这种内存管理方案则比较合适。

一般选用heap_4.c。

3、动态内存防御性编程

内存只申请不释放,运行一段时间会因为内存不足而无法运行,即内存泄露;或者操作的内存区域超出了申请的空间,访问越界即内存溢出,导致各种随机异常。对于内存操作的不稳定因素,如何进行防御性编程,可以在调试阶段发现问题?

简单的说就是内存分配的时候,记录申请内存的函数名(或者扩展加上申请时间),申请内存大小的基础上额外增加空间,在其首尾加入特殊的标志位,释放该内存前对标志位进行校验;如果校验不通过,则将申请该内存的函数名打印出来,表示出现了内存溢出。也支持随时打印当前动态内存的使用情况,查看某些函数申请的内存释放一直未被释放,人工判断是否内存泄露。

下面是完整源码:

//pal_memory.h
#ifndef _PAL_MEMORY_H
#define _PAL_MEMORY_H

//配置是否开启内存记录功能
#define __MEMORY_DEBUG__

typedef unsigned char   uint8_t;
typedef unsigned int    uint32_t;

extern void *chengj_pal_memory_malloc(uint32_t size, const char *func);
extern void chengj_pal_memory_free(void **pv);
extern void chengj_pal_memory_record_print(void);

#define chengj_malloc(size)     chengj_pal_memory_malloc(size, __FUNCTION__)
#define chengj_free(pv)         chengj_pal_memory_free(&pv)

#endif  /* _PAL_MEMORY_H */

具体实现:

/**********************************************************************
 * 
 * Copyright(c)  embedded-systems rights reserved
 * 
 * Description:
 *        memory management 
 *
 *      [微信公众号: 嵌入式系统]
 * 
 *********************************************************************/
#include <stddef.h>
#include <string.h>
#include "pal_memory.h"

//适配平台内存管理接口
#define PAL_MALLOC  malloc
#define PAL_FREE    free

#if defined (__MEMORY_DEBUG__)

#define MEMORY_RECORD_COUNT_MAX 100

//len[4]+head[4]+...[data]...+tail[2]
#define MEMORY_EXTRA_SIZE 10

//magic
#define MEMORY_DATA_MAGIC_HEAD 0x43
#define MEMORY_DATA_MAGIC_TAIL 0x4A

typedef struct
{
    const char *func_name;
    void *pointer;
    //可扩展保存 时间戳 等信息
} memory_record_struct;

//记录申请内存的函数
static memory_record_struct chengj_memory_record[MEMORY_RECORD_COUNT_MAX] = {0};

#endif /* __MEMORY_DEBUG__ */

/*
 *输出未被释放的申请函数名和指针地址
 */
void chengj_pal_memory_record_print(void)
{
#if defined (__MEMORY_DEBUG__)
    uint32_t i = 0;

    for(; i < MEMORY_RECORD_COUNT_MAX; i++)
    {
        if(chengj_memory_record[i].pointer != NULL)
        {
            printf("[%d] %s()\r\n", i, chengj_memory_record[i].func_name);
        }
    }
#endif /* __MEMORY_DEBUG__ */
}

/*
 *malloc
 */
void *chengj_pal_memory_malloc(uint32_t size, const char *func)
{
    void *pv = NULL;
    uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
    uint8_t *pdata;
#endif

    if(size == 0 || func == NULL)
    {
        return NULL;
    }

#if defined (__MEMORY_DEBUG__)
    size = size + MEMORY_EXTRA_SIZE;
#endif

    pv = PAL_MALLOC(size);

    if(pv == NULL)
    {
        return NULL;
    }
    memset(pv, 0, size);

#if defined (__MEMORY_DEBUG__)

    pdata = (uint8_t *)pv;

    pdata[0] = (size >> 24) & 0xFF;
    pdata[1] = (size >> 16) & 0xFF;
    pdata[2] = (size >> 8) & 0xFF;
    pdata[3] = size & 0xFF;

    pdata[4] = MEMORY_DATA_MAGIC_HEAD;
    pdata[5] = MEMORY_DATA_MAGIC_HEAD;
    pdata[6] = MEMORY_DATA_MAGIC_HEAD;
    pdata[7] = MEMORY_DATA_MAGIC_HEAD;

    pdata[size - 2] = MEMORY_DATA_MAGIC_TAIL;
    pdata[size - 1] = MEMORY_DATA_MAGIC_TAIL;

    for(; i < MEMORY_RECORD_COUNT_MAX; i++) //过多不记录
    {
        if(chengj_memory_record[i].pointer == NULL)
        {
            chengj_memory_record[i].func_name = func;
            chengj_memory_record[i].pointer = pv;
            break;
        }
    }
    
    return &pdata[8];

#else

    return pv;

#endif /* __MEMORY_DEBUG__ */

}

/*
 *free
 */
void chengj_pal_memory_free(void **pv)
{
    uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
    uint32_t size;
    uint8_t *pdata;
    uint8_t error = 0;
#endif

    if(pv == NULL || *pv == NULL)
    {
        return;
    }

#if defined (__MEMORY_DEBUG__)

    pdata = (uint8_t *)(*pv) - 8;
    *pv = (void*)pdata;

    size = ((pdata[0] << 24) | (pdata[1] << 16) | (pdata[2] << 8) | (pdata[3]));

    if(size <= MEMORY_EXTRA_SIZE)
    {
        error = error | 0x01;
    }

    if((pdata[4] != MEMORY_DATA_MAGIC_HEAD) || (pdata[5] != MEMORY_DATA_MAGIC_HEAD)\
            || (pdata[6] != MEMORY_DATA_MAGIC_HEAD) || (pdata[7] != MEMORY_DATA_MAGIC_HEAD))
    {
        error = error | 0x02;
    }

    if((pdata[size - 2] != MEMORY_DATA_MAGIC_TAIL) || (pdata[size - 1] != MEMORY_DATA_MAGIC_TAIL))
    {
        error = error | 0x04;
    }


    for(; i < MEMORY_RECORD_COUNT_MAX; i++)
    {
        if(chengj_memory_record[i].pointer == *pv)
        {
            if(error != 0)
            {
                if(chengj_memory_record[i].func_name != NULL)
                {
                    printf("memory error 0x%02X %s()\r\n", error, chengj_memory_record[i].func_name);
                }
                else
                {
                    printf("memory error 0x%02X %p\r\n", error, *pv);
                }
            }
            memset(&chengj_memory_record[i], 0, sizeof(memory_record_struct));
            break;
        }
    }

    if(error != 0) 
    {
        //ASSERT调试
        return;
    }
#endif /* CY_PAL_MEMORY_DEBUG */

    PAL_FREE(*pv);
    *pv = NULL;

    return;
}

可以测试下效果:

#include "pal_memory.h"

//微信公众号: 嵌入式系统
//申请10字节但使用20字节
void test(void)
{
    uint8_t *p;
    uint8_t i;

    p=chengj_malloc(10);
    for(i=0;i<20;i++)
    {
        p[i]=i;
    }
    chengj_free(p);
}


int main(int argc, char *argv[])
{
    printf("embedded-system \r\n");
    test();
    return 0;
}

运行结果:

embedded-system
memory error 0x04 test()

表示test函数内申请的一段内存使用时溢出,尾部标记数据被覆盖。

也可以在memory_record_struct增加时间戳成员,记录内存申请时间,再扩展void chengj_pal_memory_record_print(void) 打印内存使用情况,查看长时间申请未释放的内存使用情况。针对日志信息缩小内存异常的范围,关于软件bug调试与解决,可以参考《嵌入式软件bug从哪来,怎么去》。

4、小节

内存记录调试方法,浪费了一定量的内存空间,而且不能排除问题,只是提早监测到异常,但对软件稳定性仍有较大意义,可以快速解决内存问题。建议只在debug版本启用,正式发布的release版本关闭记录功能。

已剪辑自: https://mp.weixin.qq.com/s?__biz=Mzg3OTQ3NDQ2Mg==&mid=2247484804&idx=1&sn=e3d40981b1cf2cceaaacb84901a2d88a&chksm=cf02a7f5f8752ee3b50f37003866440837019d3e080585b5f975dc8c9222cc8b8e044d7955a1&scene=178&cur_album_id=1856108351068143619#rd

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态内存管理及防御性编程 的相关文章

  • Windows下使用C语言的周期UDP编程同时发送和接收网络调试助手数据

    代码 xff1a span class token macro property span class token directive hash span span class token directive keyword include
  • Windows下使用C语言的周期多路UDP编程发送和接收网络调试助手数据

    代码 xff1a span class token macro property span class token directive hash span span class token directive keyword include
  • 哪一件事让你忽然意识到打工永无出路?

    我发誓我一定要做点什么 xff0c 所以我自己开了个公司 大概我任职某公司CTO的时候吧 我意识到了所谓的战略 技术 管理等综合能力 xff0c 不如销售跑去和客户喝一顿 xff0c 上千万的单就来了 我意识到我所谓的研发战略 xff0c
  • 如何让一个 C 语言项目调用另一个 C++ 项目中某些类所提供的接口?

    目前問題是這樣的 xff1a 有兩個項目 一個項目是用 C 43 43 寫的 裏面提供了一個輸入輸出接口 後來從外面弄來了另外一個項目 用 C 寫的 現在需要將 C 項目中所使用的原有接口替換為使用我們的 C 43 43 項目中提供的接口
  • STM32学习(二)

    常用开发工具简介 安装仿真器驱动 DAP仿真器免驱ST LINK仿真器驱动安装方法 xff1a A盘 6 xff0c 软件资料 1 xff0c 软件 5 xff0c 其他软件 ST LINK驱动及教程 为什么要安装CH340 USB串口驱动
  • 为什么我身边有人说我没有眼力见,眼力见是什么,应该怎么做?

    眼力见 主要描述一个人善于察言观色 为人殷勤 xff0c 还很有礼貌的素养 其实 xff0c 只要把自己的 姿态放低 xff0c 眼力见 自然就见长 大家有没有这种感觉 xff0c 工作中不少领导 前辈经常把 眼力见 这个词挂在嘴边 xff
  • 反应慢,没有眼力劲,看起来给别人一种老实的感觉,做起事情不太利索,接触让别人感觉有点傻。怎么办?

    内向的人 xff0c 缺乏社交 xff0c 就缺乏来自从小到大关系网的 经验叠加 比如 xff0c 你没钱 xff0c 但你有个哥们儿是富二代 xff0c 你们经常一起混 xff0c 那么你会从他身上得到很多 富有家庭对待事务的态度和想法
  • 遇事没有眼力见,反应不灵活,该怎么改善?

    说实话 xff0c 我也属于这种类型的人 说白了就是头脑简单 这个跟成长环境有关系 xff0c 没有经历过需要你去思考太多 xff0c 分析太多 xff0c 最后根据情况作出当时应该 需要做出的回应 和经历有关 如果你现在认为这个事情非常重
  • 如何快速读懂开源代码?

    文章目录 RUN起来 调试 把控关键数据结构和函数 从小的开始 关注一个模块 工具 一 阅读开源代码存在的一些误区 二 阅读代码的心态 三 阅读源码与 辅助材料 四 如何阅读开源代码 gdb 高级调试实战教程 电子书下载链接 xff1a 1
  • 关于我转行嵌入式的那些事

    文章目录 为什么想转行了 xff1f 一 工作环境问题 二 无休止的出差加班和混乱的作息时间 三 工作压力大 四 薪资上限低 xff0c 行业前景差 为什么选择嵌入式 转行前的学习 一 单片机开发 二 Linux应用开发 三 Linux驱动
  • QT的UDP通信详解

  • 这一年我的书单!

    已剪辑自 https mp weixin qq com s Uy3hsbQQY3U4h43rdWr8qA 昨天写了2022年的一些感悟 xff1a 我这一年的感悟 xff0c 在文章里我提到读书在精不在多 xff0c 能指导生活工作中实践的
  • 如何专业地命名嵌入式软件版本?

    已剪辑自 https mp weixin qq com s F XhvYy0IjTrdHIu2BLhNA 不知道大家发布软件的时候 xff0c 版本号是怎么命名的 xff1f 最常见的就是V1 0 0这种简单的形式命名 甚至有些同事直接用V
  • 万字长文细说 Code Review 的正确姿势

    已剪辑自 https mp weixin qq com s GWLlRkF1b6LnyIYZi NSdQ 随着研发团队规模的逐步扩大 xff0c 新项目及新成员越来越多 xff0c 如何做好 code review xff0c 把控研发人员
  • 50条C语言奇技淫巧,精品干货!

    已剪辑自 https mp weixin qq com s vvdvVMVmx3i 6eXjUUYfBQ 本文汇总了50条C语言奇技淫巧 xff0c 希望能对大家有所帮助 01 宏定义用do while 0 如果定义的宏函数后面有多条语句
  • FreeRTOS学习(一)

    裸机与RTOS对比 裸机 xff1a 又称为前后台系统 xff0c 前台系统指的是中断服务函数 xff0c 后台系统指的大循环 xff0c 即应用程序 实时性差 xff1a xff08 应用程序轮流执行 xff09 delay xff1a
  • 如何画架构图?

    在我们做系统架构设计时 xff0c 如何快速的向外界传达我们的设计思路 4 43 1试图适合我们厘清思路 表达自己的想法 在我们汇报 xff0c 争取领导层的认同支持更适合用架构图来表述我们的观点 架构图包括总体架构 逻辑架构 应用架构 技
  • 怎么做串口调试软件?

    嗯 说一下我自己写的串口助手吧 xff0c 名字叫 Bittly xff0c 样子呢长下面这个样子 Bittly 指令调试界面 1 需求确认 一开始使用的是类似于XCOM或者SSCOM之类的串口调试助手 xff0c 他们的优点是体积小 xf
  • 【需求专题】如何写好需求——INCOSE需求编写指南(1)

    已剪辑自 https mp weixin qq com s Z5VBTyV6j07JylDdOsFSxQ 编者按 如何写好需求是INCOSE 需求工作组编写的需求文本化表达指南 本指南是专门讲述如何在系统工程中对需求进行文本化表达 xff0
  • 怎么提高自己的系统设计和架构理论水平?

    文章目录 前言 1 无锁化 1 1 串行无锁 1 2 结构无锁 2 零拷贝 2 1 内存映射 2 2 零拷贝 3 序列化 3 1 分类 3 2 性能指标 3 3 选型考量 4 池子化 4 1 内存池 4 2 线程池 4 3 连接池 4 4

随机推荐

  • 30+男生程序员中年如何破局

    已剪辑自 https zhuanlan zhihu com p 596751971 1 最顶级的程序员根据自己的经验拼paper 拼专利 xff0c 成为不可替代的专家 最厉害的程序员拼的不是代码写的多牛逼 而是有多少paper多少顶尖专利
  • 为啥AI难落地?

    总在说AI落地难 xff0c 那为啥难落地 xff1f 以最典型的智慧城市业务来说 xff0c 就是接入网络摄像头 xff0c 然后识别里面的人 xff0c 判断是不是抽烟 打架 闯红灯 不带安全帽等 首先是连接网络摄像机 xff0c GB
  • 搞技术,如何写好技术文档?

    已剪辑自 https mp weixin qq com s OtSwtMyeifoc7ED35a vEA 嵌入式方案设计文档 xff0c 到底应该怎么写 xff1f 你是不是从来没有想过这个问题 xff1f 很多技术人自己非常轻视技术文档的
  • 用125行C语言编写一个简单的16位虚拟机

    已剪辑自 https mp weixin qq com s ikrpGtssoKpumHXhrQdh8Q 博文地址 xff1a 改博文用图文代码的方式详细描述了实现的具体过程 xff0c 包含每一条指令的含义 系统虚拟机 xff0c 可完全
  • RT-Thread操作系统的FreeRTOS兼容层

    已剪辑自 https mp weixin qq com s 2BjJyieMr97NQhO76DQ3hw Github地址 https github com RT Thread packages FreeRTOS Wrapper 本项目是2
  • 嵌入式开发既要代码小,又想速度快,该如何优化?

    已剪辑自 https mp weixin qq com s HaoPN0upS8OEheXpSHWBFA 素材来源 xff1a 网络素材 整理 xff1a 技术让梦想更伟大 李肖遥 对程序进行优化 xff0c 通常是指优化程序代码或程序执行
  • 小白学C语言编程(for语句无限制循环)

    问题 xff1a 怎么样无限制输出一个比1大的数 xff1f span class token macro property span class token directive keyword include span span clas
  • 嵌入式开发打印,我放弃了printf

    已剪辑自 https mp weixin qq com s GGZ38dUITlS6w9hnMbzsvg 对于printf xff0c 相信不用我过多介绍 xff0c 大家在初学C语言时用得最多的信息输出接口函数应该就是printf了 对于
  • 自动驾仿真测试平台干货内容梳理

    已剪辑自 https mp weixin qq com s Ftv2rgiGW6FGVQgMz4A9PQ 1 自动驾驶仿真平台的关键构成 自动驾驶仿真平台需支持车辆动力学仿真 环境感知传感器仿真 交通场景仿真等 xff1b 车辆动力学仿真
  • 自动驾驶域控制器开发和量产的挑战

    已剪辑自 https mp weixin qq com s Sh4ONJxrmvDbfWlcDnXYtQ 过去十多年的汽车智能化和信息化发展产生了一个显著结果就是ECU芯片使用量越来越多 从传统的引擎控制系统 安全气囊 防抱死系统 电动助力
  • STM32属于哈佛结构还是冯诺依曼结构?

    现代的CPU基本上归为冯诺伊曼结构 xff08 也称普林斯顿结构 xff09 和哈佛结构 我们常见的X86架构是冯 诺依曼结构 xff0c 而ARM架构是哈佛结构 一个广泛用于桌面端 xff08 台式 笔记本 服务器 工作站等 xff09
  • 2022年度复盘和2023年目标:在焦虑中探索,在体验中成长,在开放中升华

    文章目录 2022年度复盘工作 xff1a 焦虑 xff0c 认知 xff0c 提升个人工作 xff1a 工作态度需要提升团队工作 xff1a 尊重 真诚 准确清晰完善感悟 个人成长硬能力 xff1a 学习 博客软能力 xff1a 知乎 B
  • 技术部门Leader是不是一定要技术大牛担任?

    现在在腾讯做技术Leader xff0c 之前在阿里 xff0c 绿厂也带过技术团队 xff0c 每家的情况有共同点也有区别 现在总结下来 xff0c 除去特别通用的技术 责任心 沟通 主动性这些 xff0c 作为Leader很关键的个人素
  • 一个人该怎样找到自己真正热爱和擅长的事,并以此规划自己的人生?

    文章目录 一个人该怎样找到自己真正热爱和擅长的事 xff0c 并以此规划自己的人生 xff1f 一 有关擅长的4个错误认知 二 做好这3步 xff0c 拥有擅长之事 1 生成兴趣清单 2 缩小选择范围 3 练练看 三 写在最后 下面这张图
  • 一个适用于单片机的开源网络协议栈

    已剪辑自 https mp weixin qq com s Vpi4E9T5BUo cdCE692V A 移植及使用说明 协议栈支持主流的ARM Cortex系列MCU xff0c 支持Keil MDK IAR等常见IDE 移植的核心工作就
  • 嵌入式软件分层隔离的典范

    已剪辑自 https mp weixin qq com s T7EJEAuXo1CCJa5vPAPVvg 引言 xff1a 嵌入式软件开发分层 模块化是理想状态 xff0c 实际开发中因各种限制而有所取舍 xff0c 但这不妨碍学习参考优秀
  • 软件定时器库

    前后台系统和多任务操作系统 xff0c 在简单功能上差别不大 xff0c 唯一不顺手的就是前后台系统没有合适的软件定时器 封装一层软件定时器接口 xff0c 对后续功能开发事半功倍 定义结构体数组 xff0c 其基础成员如下 xff1a s
  • 解决Raspberry系统ssh默认关闭的问题

    问题描述 xff1a 最近 xff0c 自己动手给树莓派3B安装了一个最新的系统 xff0c 本想通过SSH远程登录系统的时候 xff0c 发现登录不了 xff0c 具体表现为能ping通 xff0c 但是Xshell就是连不上 xff0c
  • mbedtls 库基础及其应用

    文章目录 1 引言1 1 为什么要加密1 2 SSL TLS协议的历史 2 SSL TLS演化2 1 明文时代2 2 对称加密时代2 3 非对称加密时代2 4 公证时代2 5 TLS协议时代2 6 TLS的应用 3 mbedtls3 1 软
  • 动态内存管理及防御性编程

    概述 xff1a C语言的优势是可以直接访问内存地址 xff0c 也就是指针操作 xff0c 但其缺陷也是因为直接内存访问 如何通过防御性编程提前发现问题 xff0c 尽可能减少内存异常产生的后果 xff0c 就是本文的重点 1 内存划分