[转载] 内存越界

C++内存管理陷阱

*** glibc detected *** free(): invalid pointer:
*** glibc detected *** malloc(): memory corruption:
*** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
*** glibc detected *** double free or corruption (!prev): 0x0000000000a01f40 ***
*** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***


你是否遇到过这样的情况,太沮丧了,程序总是无端coredump,gdb到core文件里面也看不出个所以然来,这对于一个大型的商业系统来说太令人恐怖了,事故随时可能发生。

遇到棘手的问题,慌张是没用的,解决不了任何问题。先坐下来,喝杯茶,舒缓一下神经。

内存问题始终是c++程序员需要去面对的问题,这也是c++语言的门槛较高的原因之一。通常我们会犯的内存问题大概有以下几种:

1.内存重复释放,出现double free时,通常是由于这种情况所致。
2.内存泄露,分配的内存忘了释放。
3.内存越界使用,使用了不该使用的内存。
4.使用了无效指针。
5.空指针,对一个空指针进行操作。

对于第一种和第二种,第五种情况,就不用多说,会产生什么后果大家应该都很清楚。

第四种情况,通常是指操作已释放的对象,如:
1.已释放对象,却再次操作该指针所指对象。
2.多线程中某一动态分配的对象同时被两个线程使用,一个线程释放了该对象,而另一线程继续对该对象进行操作。

我们重点探讨第三种情况,相对于另几种情况,这可以称得上是疑难杂症了(第四种情况也可以理解成内存越界使用)。

内存越界使用,这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易重现的错误,才是最致命的,一旦出错破坏性极大。

什么原因会造成内存越界使用呢?有以下几种情况,可供参考:
例1:
        char buf[32] = {0};
        for(int i=0; i<n; i++)// n < 32 or n > 32
        {
            buf[i] = 'x';
        }
        ....        
例2:
        char buf[32] = {0};
        string str = "this is a test sting !!!!";
        sprintf(buf, "this is a test buf!string:%s", str.c_str()); //out of buffer space
        ....    
例3:
        string str = "this is a test string!!!!";
        char buf[16] = {0};
        strcpy(buf, str.c_str()); //out of buffer space
        
类似的还存在隐患的函数还有:strcat,vsprintf等
同样,memcpy, memset, memmove等一些内存操作函数在使用时也一定要注意。
        
当这样的代码一旦运行,错误就在所难免,会带来的后果也是不确定的,通常可能会造成如下后果:

1.破坏了堆中的内存分配信息数据,特别是动态分配的内存块的内存信息数据,因为操作系统在分配和释放内存块时需要访问该数据,一旦该数据被破坏,以下的几种情况都可能会出现。 
        *** glibc detected *** free(): invalid pointer:
        *** glibc detected *** malloc(): memory corruption:
        *** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
        *** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***        

2.破坏了程序自己的其他对象的内存空间,这种破坏会影响程序执行的不正确性,当然也会诱发coredump,如破坏了指针数据。

3.破坏了空闲内存块,很幸运,这样不会产生什么问题,但谁知道什么时候不幸会降临呢?

通常,代码错误被激发也是偶然的,也就是说之前你的程序一直正常,可能由于你为类增加了两个成员变量,或者改变了某一部分代码,coredump就频繁发生,而你增加的代码绝不会有任何问题,这时你就应该考虑是否是某些内存被破坏了。

排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。
检查所有的内存操作函数,检查内存越界的可能。常用的内存操作函数:
sprintf snprintf 
vsprintf vsnprintf
strcpy strncpy strcat 
memcpy memmove memset bcopy

如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

在Keil环境下开发STM32项目时,遇到`HardFault_Handler`是一种常见的硬件异常,通常表示程序执行过程中发生了不可恢复的错误,例如非法指令、内存访问冲突、堆栈溢出或未对齐的内存访问等。处理这类错误需要系统性地排查代码和硬件配置。 ### 调试步骤与解决方案 #### 1. 启用调试器并查看堆栈信息 使用Keil的调试功能,配合ST-Link或其他调试器,进入`HardFault_Handler`中断服务程序后,查看调用堆栈和寄存器状态。特别是`PC`(程序计数器)和`LR`(链接寄存器)的值,可以帮助定位发生错误的代码位置。 #### 2. 检查内存访问问题 确保所有指针访问都指向有效的内存区域,避免访问未初始化的指针或越界访问数组。例如,以下代码可能导致HardFault: ```c int *ptr = NULL; *ptr = 10; // 非法内存写入,触发HardFault ``` #### 3. 配置默认异常处理 在启动文件(如`startup_stm32f103xb.s`)中,可以修改`HardFault_Handler`的实现,添加调试输出或断点,以便更早地捕获错误源头。例如: ```c void HardFault_Handler(void) { __disable_irq(); while (1) { // 添加调试输出或断点 } } ``` #### 4. 使用Fault异常寄存器分析 STM32的Cortex-M内核提供了多个故障状态寄存器,如`HFSR`(HardFault Status Register)、`CFSR`(Configurable Fault Status Register)和`MMAR`(MemManage Fault Address Register)等。通过读取这些寄存器的值,可以更精确地判断错误类型。例如: ```c #include "core_cm3.h" void HardFault_Handler(void) { uint32_t hfsr = SCB->HFSR; uint32_t cfsr = SCB->CFSR; uint32_t mmar = SCB->MMAR; // 打印寄存器值用于分析 while (1); } ``` #### 5. 检查堆栈溢出 如果程序中存在递归调用或局部变量占用过多栈空间,可能导致堆栈溢出。检查`stack`段的大小配置,确保堆栈足够大。可以在链接脚本或启动文件中调整堆栈大小。 #### 6. 检查中断配置 确保所有中断服务程序正确实现,且未发生中断嵌套冲突。例如,未正确配置NVIC优先级可能导致高优先级中断打断低优先级中断,从而引发异常。 #### 7. 使用静态代码分析工具 Keil MDK提供了静态代码分析工具,可以在编译阶段发现潜在的代码问题,如未初始化变量、指针越界等。 #### 8. 启用看门狗复位 在某些情况下,可以启用看门狗定时器,当程序卡死在`HardFault_Handler`时自动复位系统,提高系统的健壮性。 --- ### 示例代码:增强的HardFault_Handler 以下是一个增强版的`HardFault_Handler`实现,包含基本的寄存器打印功能,有助于调试: ```c #include "core_cm3.h" #include <stdio.h> void HardFault_Handler(void) { uint32_t hfsr = SCB->HFSR; uint32_t cfsr = SCB->CFSR; uint32_t mmar = SCB->MMAR; uint32_t bfar = SCB->BFAR; uint32_t lr = 0, pc = 0; // 获取当前PC和LR值 asm volatile ("mov %0, lr" : "=r" (lr)); asm volatile ("mov %0, pc" : "=r" (pc)); printf("HardFault occurred!\n"); printf("HFSR: 0x%08X\n", hfsr); printf("CFSR: 0x%08X\n", cfsr); printf("MMAR: 0x%08X\n", mmar); printf("BFAR: 0x%08X\n", bfar); printf("LR: 0x%08X\n", lr); printf("PC: 0x%08X\n", pc); while (1); } ``` --- ### 常见错误类型与排查建议 - **MemManage Fault**:通常与内存访问错误有关,如访问非法地址或未对齐访问。检查指针操作和数组边界。 - **BusFault**:可能由指令或数据访问失败引起,常见于外部存储器访问错误或中断向量表配置不当。 - **UsageFault**:多由未定义指令、除零操作或未对齐访问引起,检查代码逻辑和编译器优化设置。 --- ### 总结 处理`HardFault_Handler`的关键在于结合调试工具、寄存器分析和代码审查,逐步缩小问题范围。通过启用详细的异常信息打印和合理配置堆栈、中断优先级,可以有效提升调试效率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值