You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多子进程管道通信中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),我们应该直接遍历二进制流,按照帧的规则来拆分,完全避开字符串函数的限制。以下是实现思路和代码:

实现思路

  1. 遍历二进制流,每次定位END_FRAME(0x04)的位置,确定当前帧的结束点。
  2. 先统计总帧数量,再分配对应大小的数组内存。
  3. 为每个帧单独分配内存,复制对应的数据到数组中。
  4. 通过输出参数返回帧的数量,解决原函数无法告知外部帧数量的问题。

修正后的代码

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:54:09