多子进程管道通信中Acquittal帧流的无符号字符数组拆分问题及代码故障排查
解决管道帧流拆分与strtok()故障的问题
让我来帮你分析下当前代码的核心问题,然后给出可行的解决方案:
一、当前代码的问题拆解
1. strtok()无法正常工作的根源
- 二进制流不兼容字符串函数:你的帧流是
unsigned char类型的二进制数据,但strtok()、strlen()、strcpy()都是处理以\0结尾的字符串的工具。如果帧数据中包含\0字节,这些函数会直接截断数据,导致长度计算错误、拆分失败。 - 调用顺序完全错误:你先调用了
strtok(tempFrameStream, ...),但此时tempFrameStream还没被赋值,是malloc出来的垃圾数据,这会导致第一次strtok直接返回NULL,后续循环直接跳过。 - 分隔符参数传递错误:
strtok的第二个参数需要是以\0结尾的字符串,你传入(char *)END_FRAME,实际上是把单个0x04字符的地址传进去,但这个地址后面没有\0,strtok会读取内存直到遇到\0,这属于未定义行为。
2. 段错误的触发原因
- 帧数量计算错误:
getNbFrames用strlen计算流长度,二进制流中如果有\0,会导致长度计算偏小,进而nbFrame不准确,后续malloc的数组大小不够,访问时直接越界。 - 非法free操作:
extractedFrame是strtok返回的指向tempFrameStream内部的指针,不是通过malloc分配的,调用free(extractedFrame)会直接损坏堆结构,引发段错误。 - 空指针访问:当循环中
extractedFrame为NULL时,你仍然调用strlen(extractedFrame),这会直接触发段错误。
二、正确解决方案:基于帧结构的二进制解析
因为你的帧有明确的结构和结束符(0x04),我们应该直接遍历二进制流,按照帧的规则来拆分,完全避开字符串函数的限制。以下是实现思路和代码:
实现思路
- 遍历二进制流,每次定位
END_FRAME(0x04)的位置,确定当前帧的结束点。 - 先统计总帧数量,再分配对应大小的数组内存。
- 为每个帧单独分配内存,复制对应的数据到数组中。
- 通过输出参数返回帧的数量,解决原函数无法告知外部帧数量的问题。
修正后的代码
#include <stdlib.h> #include <string.h> enum frameComponents { END_FRAME = (unsigned char)0x04, CMD_A_SET = 0xA2, CMD_A_DUMP = 0xD2, CMD_A_LOOKUP = 0xB2, ERROR_SUCCESS = 0x20, ERROR_INTERNAL = 0x50, ERROR_NOT_FOUND = 0x44 }; // 辅助函数:计算单个帧的长度(包含END_FRAME) static size_t getFrameLength(const unsigned char *stream, size_t stream_len) { size_t i = 0; while (i < stream_len && stream[i] != END_FRAME) { i++; } // 返回有效帧长度,找不到结束符则返回0 return (i < stream_len) ? i + 1 : 0; } // 返回拆分后的帧数组,out_nb_frames用于输出帧的数量 unsigned char **deserialisation(const unsigned char *frameStream, size_t stream_len, unsigned int *out_nb_frames) { if (!frameStream || stream_len == 0 || !out_nb_frames) { *out_nb_frames = 0; return NULL; } unsigned int nbFrame = 0; size_t pos = 0; // 第一步:统计帧的总数量 while (pos < stream_len) { size_t frame_len = getFrameLength(frameStream + pos, stream_len - pos); if (frame_len == 0) break; // 找不到结束符,停止统计 nbFrame++; pos += frame_len; } *out_nb_frames = nbFrame; if (nbFrame == 0) return NULL; // 第二步:分配帧数组内存 unsigned char **framesArray = malloc(nbFrame * sizeof(unsigned char *)); if (!framesArray) { *out_nb_frames = 0; return NULL; } // 第三步:拆分每个帧并复制数据 pos = 0; for (unsigned int i = 0; i < nbFrame; i++) { size_t frame_len = getFrameLength(frameStream + pos, stream_len - pos); framesArray[i] = malloc(frame_len); if (!framesArray[i]) { // 分配失败,释放已分配的内存避免泄漏 for (unsigned int j = 0; j < i; j++) { free(framesArray[j]); } free(framesArray); *out_nb_frames = 0; return NULL; } memcpy(framesArray[i], frameStream + pos, frame_len); pos += frame_len; } return framesArray; } // 使用示例 /* int main() { unsigned char input[] = {0xd2,0x04,0x20,0x04,0xd2,0x05,0x20,0x04,0xd2,0x06,0x20,0x04,0xd2,0x07,0x20,0x04,0xb2,0x08,0x20,0x01,0x08,0x62,0x6f,0x6e,0x6a,0x6f,0x75,0x72,0x04}; unsigned int nb_frames = 0; unsigned char **frames = deserialisation(input, sizeof(input), &nb_frames); if (frames) { for (unsigned int i = 0; i < nb_frames; i++) { // 这里可以添加帧解析逻辑 free(frames[i]); } free(frames); } return 0; } */
代码说明
- 全程针对二进制数据操作,完全避开字符串函数的限制,支持包含
\0的帧数据。 - 增加了完整的错误处理,分配失败时会释放已分配的内存,避免内存泄漏。
- 通过
out_nb_frames参数返回帧的数量,解决了原函数无法告知外部帧数量的问题。
三、额外优化建议
如果需要进一步解析帧的内容(比如提取CMD、NODE ID等),可以在拆分后根据帧结构进行解析,示例代码如下:
void parseFrame(const unsigned char *frame, size_t frame_len) { if (frame_len < 3) return; // 至少要有CMD、NODE ID、ERROR FLAG三个字段 unsigned char cmd = frame[0]; unsigned char node_id = frame[1]; unsigned char error_flag = frame[2]; // 根据命令和错误标志处理后续数据 if (cmd == CMD_A_LOOKUP && error_flag == ERROR_SUCCESS) { if (frame_len >= 4 + 2) { // 4字节固定部分 + 2字节数据长度 // 假设数据长度是大端编码,根据实际协议调整 unsigned short data_len = (frame[3] << 8) | frame[4]; const unsigned char *data = frame + 5; // 这里可以添加数据处理逻辑 } } }
内容的提问来源于stack exchange,提问作者MrSolarius
相关产品推荐
相关产品推荐

