动态申请内存与释放时的异常---Byte越界(CheckBytes函数的内部机制)

本文详细介绍了在C++中使用宽字符进行内存申请时,遇到LoadLibrary函数异常的问题,以及如何通过多申请内存空间解决该问题。通过分析内存布局和CheckBytes函数的工作原理,解释了异常产生的原因,并提供了避免此类问题的方法。

http://blog.youkuaiyun.com/zp373860147/article/details/6992647

今天在堆代码的时候,堆了下面一段代码,Debug时异常,Release没问题。

[html]  view plain copy
  1. const std::string &sFilename;  
  2. wchar_t  *wcstring = (wchar_t *)malloc(sizeof(wchar_t)*(sFilename.length()-1));  
  3.   m_hDLL = ::LoadLibrary(/*stringToLPCWSTR(sFilename)*/stringToLPCWSTR(sFilename,wcstring));  
  4.   if (wcstring)  
  5.   {  
  6.       delete []wcstring;  
  7.   }  
  8.   wcstring = NULL;  
其实很简单,得到一个const std::string &类型的参数sFilename,想通过它来加载dll,但是::LoadLibrary的参数是LPCWSTR类型,就自己写个函数

[html]  view plain copy
  1. LPCWSTR stringToLPCWSTR(std::string orig, wchar_t * wcstring)  

来转换一下。

然后就在转换前动态申请了内存,并在使用后释放,如下:

[html]  view plain copy
  1. wchar_t  *wcstring = (wchar_t *)malloc(sizeof(wchar_t)*(sFilename.length()-1));  
  2. .......................................  
  3.   if (wcstring)  
  4.   {  
  5.       delete []wcstring;  
  6.   }  
  7.   wcstring = NULL;  
看起来貌似没问题,但是Debug异常了,问题出在delete []wcstring;这句,果断跟进去,最后跟进n层后定位到下面这个函数

[html]  view plain copy
  1. extern "C" static int __cdecl CheckBytes(  
  2.         unsigned char * pb,  
  3.         unsigned char bCheck,  
  4.         size_t nSize  
  5.         )  
  6. {  
  7.         int bOkay = TRUE;  
  8.         while (nSize--)  
  9.         {  
  10.             if (*pb++ != bCheck)  
  11.             {  
  12. /* Internal error report is just noise; calling functions all report results - JWM */  
  13. /*                _RPT3(_CRT_WARN, "memory check error at 0x%p = 0x%02X, should be 0x%02X.\n", */  
  14. /*                    (BYTE *)(pb-1),*(pb-1), bCheck); */  
  15.                 bOkay = FALSE;  
  16.             }  
  17.         }  
  18.         return bOkay;  
  19. }  

这个函数的作用是检查指定范围内的Byte是否被更改,如果被更改,返回false,如果未更改,返回true;

第一个参数是一个指针,第二个参数是用来进行比较的值,第三个参数是从指定位置(第一个指针表示)起,检测的位数。调试发现,bCheck为253,对应16进制为fd,nSize为4,下面查看内存,如图:

可以看到第一行中连续6个ab的前面有3个连续的00 ,其中第一个00是与其左边的6e共同组成一个Unicode宽字符,对应字符n,后两个连续的00组成宽字符\0,也就是从这个位置开始,CheckByte函数开始向后检测4位,看其是否是fd,由图可知,显然不是,所以返回false,抛出异常


下面,如果我申请的内存这么写:

[html]  view plain copy
  1. wchar_t  *wcstring = (wchar_t *)malloc(sizeof(wchar_t)*(sFilename.length()-1)<span style="color:#FF0000;">+2</span>);  
即多申请两个字节,内存图如下:

可以看到连续的00后只有2个fd,调试仍然失败


下面,多申请4个字节:

[html]  view plain copy
  1. wchar_t  *wcstring = (wchar_t *)malloc(sizeof(wchar_t)*(sFilename.length()-1)<span style="color:#FF0000;">+4</span>);  

可以看到现在内存中连续的00后有了四个fd,调试不再抛异常。

最后,在以上三种情况下,通过查看CheckByte的第一个参数pb发现,pb总是我们申请的内存空间的末尾位置向前移动2个字节(如果算上编译器自动加上的用于存放\0的2个字节,那就是向前移动4个字节)。也就是说,如果我们申请的内存不足够大,那么编译器将无法在其末尾找到连续4个fd,他就会认为内存越界而异常。


经验就是,在使用宽字符的时候多申请一些空间,最小也得多申请4个。

char的时候没试验,应该也是同样的道理。


将下列代码转成capl语言代码 /* * 功能:E2E Profile 1A校验自动化测试脚本 * 版本:1.0 * 说明:验证Checksum计算、Counter递增规则及ID-DataID映射 */ #include <canoe.h> #include <stdio.h> #include <stdlib.h> // ------------------------------ 常量结构体定义 ------------------------------ #define MAX_CYCLES 5 // 测试循环次数(5个完整0-14循环) #define COUNTER_MAX 0x0E // Counter最大有效值(0xE) #define COUNTER_MIN 0x00 // Counter最小有效值(0x0) #define DATA_ID_BYTES 2 // DataID字节数(低字节+高字节) #define CHECKSUM_BYTE_IDX 0 // Checksum在报文中的字节索引(0) #define COUNTER_BYTE_IDX 1 // Counter在报文中的字节索引(1) #define SIGNAL_START_IDX 2 // 信号区起始字节索引(2) // 报文IDDataID映射结构体 typedef struct { dword canId; // CAN报文ID word dataId; // 对应的DataID(低字节+高字节) } E2E_IdMap; // 预定义ID-DataID映射表(示例,需根据实际协议调整) E2E_IdMap idMap[] = { {0x344, 0xD400}, // 示例:ID 0x344 → DataID 0xD400(低字节0x00,高字节0xD4) {0x343, 0xD700}, // 示例:ID 0x343 → DataID 0xD700(低字节0x00,高字节0xD7) {0x123, 0x3210}, // 扩展示例 {0x567, 0x7650}, // 扩展示例 }; // 全局变量 byte counterHistory[MAX_CYCLES * (COUNTER_MAX + 1)]; // 记录Counter历史值(防重复) int cycleCount = 0; // 当前循环次数 int msgCount = 0; // 消息计数器(用于循环控制) // ------------------------------ CRC8_SAE_J1850函数实现 ------------------------------ byte crc8_sae_j1850(byte buffer[], int index, int length) { byte crc8 = 0x00; // 初始值0x00(Profile 1A要求) byte poly = 0x1D; // 多项式0x1D(SAE J1850) for (int i = index; i < length; i++) { crc8 ^= buffer[i]; for (int j = 0; j < 8; j++) { if (crc8 & 0x80) { crc8 = (byte)(((crc8 << 1) ^ poly) & 0xFF); } else { crc8 <<= 1; } } } return crc8; } // ------------------------------ 辅助函数:查找DataID ------------------------------ word get_data_id(dword canId) { for (int i = 0; i < elcount(idMap); i++) { if (idMap[i].canId == canId) { return idMap[i].dataId; } } return 0xFFFF; // 未找到返回无效值 } // ------------------------------ 辅助函数:验证Counter ------------------------------ byte validate_counter(byte counter, byte* history) { // 检查范围(0x0-0xE) if (counter < COUNTER_MIN || counter > COUNTER_MAX) { return 0x01; // 错误:超出范围 } // 检查重复(最近MAX_CYCLES*(COUNTER_MAX+1)次内) for (int i = 0; i < elcount(history); i++) { if (history[i] == counter) { return 0x02; // 错误:重复 } } return 0x00; // 验证通过 } // ------------------------------ 报文接收处理函数 ------------------------------ on can message * msg { byte dataId; // 当前报文对应的DataID byte rxChecksum; // 报文中的Checksum(byte0) byte rxCounter; // 报文中的Counter(byte1) byte checkBuffer[8]; // 校验数据缓冲区(8字节) byte calculatedChecksum; // 计算得到的Checksum byte counterStatus; // Counter验证状态(0=通过,非0=错误) // 仅处理目标报文(根据实际CAN ID过滤) if (msg->id not in idMap) { return; // 非目标报文,跳过 } // 步骤1:获取DataID(根据报文ID映射) dataId = get_data_id(msg->id); if (dataId == 0xFFFF) { write("Error: 未找到报文ID 0x%X 对应的DataID", msg->id); testStepFail("E2E_Checksum", "ID-DataID映射失败"); return; } // 步骤2:提取报文中的Counter和Checksum rxCounter = msg->byte[COUNTER_BYTE_IDX] & 0x0F; // 提取低4位(忽略填充位) rxChecksum = msg->byte[CHECKSUM_BYTE_IDX]; // 步骤3:验证Counter有效性 counterStatus = validate_counter(rxCounter, counterHistory); if (counterStatus != 0x00) { write("Cycle %d: Counter 0x%X 验证失败(状态码0x%X)", cycleCount, rxCounter, counterStatus); testStepFail("E2E_Counter", "Counter异常(值0x%X,状态码0x%X)", rxCounter, counterStatus); return; } // 步骤4:构造校验数据缓冲区(8字节) // byte0: DataID低字节 | byte1: DataID高字节 | byte2-7: 报文byte1-6(信号区) checkBuffer[0] = (byte)(dataId & 0x00FF); // DataID低字节 checkBuffer[1] = (byte)((dataId >> 8) & 0x00FF); // DataID高字节 checkBuffer[2] = msg->byte[SIGNAL_START_IDX]; // 报文byte2(处理后的Sig1) checkBuffer[3] = msg->byte[SIGNAL_START_IDX + 1]; // 报文byte3(处理后的Sig2) checkBuffer[4] = msg->byte[SIGNAL_START_IDX + 2]; // 报文byte4(处理后的Sig3) checkBuffer[5] = msg->byte[SIGNAL_START_IDX + 3]; // 报文byte5(处理后的Sig4) checkBuffer[6] = msg->byte[SIGNAL_START_IDX + 4]; // 报文byte6(处理后的Sig5) checkBuffer[7] = 0xFF; // 填充字节(示例中未使用字节填0xFF) // 步骤5:计算Checksum并报文比对 calculatedChecksum = crc8_sae_j1850(checkBuffer, 0, 8); if (calculatedChecksum != rxChecksum) { write("Cycle %d: Checksum校验失败(计算值0x%X ≠ 报文值0x%X)", cycleCount, calculatedChecksum, rxChecksum); testStepFail("E2E_Checksum", "Checksum不匹配(计算0x%X,接收0x%X)", calculatedChecksum, rxChecksum); return; } // 步骤6:记录Counter历史(用于重复检查) counterHistory[msgCount % elcount(counterHistory)] = rxCounter; msgCount++; // 步骤7:循环结束检查(5个循环后验证Counter是否回到0x0) if (msgCount >= MAX_CYCLES * (COUNTER_MAX + 1)) { if (rxCounter != COUNTER_MIN) { write("测试结束:5个循环后Counter未回到0x0(当前值0x%X)", rxCounter); testStepFail("E2E_Cycle", "Counter未循环回初始值"); } else { write("测试通过:5个循环内Counter无重复/错误,Checksum校验全部通过"); testStepPass("E2E_Overall", "所有校验项通过"); } cycleCount++; // 完成一个循环周期 } testStepPass("E2E_SingleMsg", "单条报文校验通过"); } // ------------------------------ 测试初始化函数 ------------------------------ on start { // 初始化Counter历史记录(填充0xFF表示未使用) for (int i = 0; i < elcount(counterHistory); i++) { counterHistory[i] = 0xFF; } write("E2E测试初始化完成,开始监控CAN报文..."); }
最新发布
09-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值