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

C语言实现CAN字节解码时带符号值解析异常问题排查

问题背景

使用C语言开发CAN字节解码函数时,已完成输入值字节序与运行系统自身字节序的适配逻辑,当前无符号值解码结果完全正常,但带符号值解码存在错误。
开发阶段默认带符号数据的最高位(MSB)为符号标志位:小端(LITTLE_ENDIAN_VAL)格式下符号位位于最后一个字节,大端(BIG_ENDIAN_VAL)格式下符号位位于第一个字节,怀疑对C语言带符号数的表示机制存在理解偏差,附现有实现代码如下:

/**
 * @brief can_interact_decode - converts array of length x containing hex bytes into a uint64
 * @param[in] const uint8_t* - const array of hex bytes
 * @param[in] const size_t - length of hex bytes array
 * @param[in] const enum can_interact_signedness - whether the bytes are storing a signed value or not. SIGNED_VAL indicates signed, UNSIGNED_VAL indicates unsigned
 * @param[in] const enum can_interact_endianness - endianess. LITTLE_ENDIAN_VAL is little, BIG_ENDIAN_VAL is big
 * @return[out] uint64_t - interpreted value as unsigned int from hex bytes, taking other params into account
 */
uint64_t can_interact_decode(const uint8_t *payload, const size_t data_len, const enum can_interact_signedness is_signed, const enum can_interact_endianness byte_order)
{
    uint64_t result; /* [0,0,0,0,0,0,0,0] */
    uint8_t* blocks; /* array of 8 */
    result = 0;
    blocks = (uint8_t*)(&result);

    if(byte_order == LITTLE_ENDIAN_VAL) {
        memcpy(blocks, payload, (is_signed ? data_len - 1 : data_len));
        blocks[7] = is_signed ? payload[data_len - 1] : blocks[7];
        result = le64toh(result); /* little endian->host byte order */
    } else if(byte_order == BIG_ENDIAN_VAL) {
        memcpy(blocks + (8 - data_len) + (is_signed ? 1 : 0), (is_signed ? payload + 1 : payload), (is_signed ? data_len - 1 : data_len));
        blocks[0] = is_signed ? payload[0] : blocks[0];
        result = be64toh(result); /* big endian->host byte order */
    }

    return result;
}
代码问题排查
  • 缺失补码符号扩展逻辑
    CAN总线传输的带符号整型统一采用二进制补码表示,当数据长度小于8字节时,若值为负,所有超出原数据位宽的高位必须全部填充为1才能得到正确的64位值。现有代码仅将符号位所在字节挪到uint64_t的最高字节位置,中间空出的高位全部保持初始值0,完全不符合补码符号扩展规则。例如2字节小端有符号值0xFF 0xFF(对应十进制-1),现有逻辑处理后得到的值为0xFF000000000000FF,和正确值0xFFFFFFFFFFFFFFFF存在本质差异。
  • 有符号值的字节拷贝位置完全错位
    • 小端场景:代码直接拷贝data_len-1个字节到result的起始内存位置,再单独把符号位所在的最后一个字节放到result的第7个字节位置,相当于把原数据的低data_len-1字节放在64位值的最低位,符号位直接扔到最高位,中间空出的6个字节既无值填充也无顺序对齐,字节位置完全错误。例如1字节小端有符号值0xFF(对应-1),现有逻辑拷贝0字节到result起始位置,仅将第7字节设为0xFF,转换后得到0xFF00000000000000,和正确值偏差极大。
    • 大端场景:拷贝偏移计算blocks + (8 - data_len) + (is_signed ? 1 : 0)逻辑错误。大端格式下输入的第一个字节是含符号位的最高有效字节,后续字节按位权从高到低排列,现有代码把符号位单独放到result的第0字节,剩余data_len-1字节从8-data_len+1偏移位置开始拷贝,直接导致次高字节位置比符号位还高,字节顺序完全混乱。
  • 字节序转换函数使用场景错误
    le64toh/be64toh的作用是转换长度为8字节、按对应字节序排列的完整整数到主机字节序,现有代码在调用转换函数前,result内存中的字节根本不是按目标字节序排列的完整64位值,转换结果自然不符合预期。
修复方案
  1. 先按无符号值的处理逻辑,根据输入字节序把所有payload字节正确拼装到64位result的对应位权位置,保证原数据的每一位在64位值中的位权和实际定义一致
  2. 如果是有符号值,判断原数据最高位(符号位)是否为1:若为1,将所有高于原数据位宽的高位全部置1,完成补码符号扩展
  3. 不要提前拆分符号位单独拷贝,避免字节错位问题。

参考修复代码:

uint64_t can_interact_decode(const uint8_t *payload, const size_t data_len, const enum can_interact_signedness is_signed, const enum can_interact_endianness byte_order)
{
    uint64_t result = 0;
    // 先按无符号规则拼装字节
    if (byte_order == LITTLE_ENDIAN_VAL) {
        for (size_t i = 0; i < data_len; i++) {
            result |= (uint64_t)payload[i] << (i * 8);
        }
    } else { // BIG_ENDIAN_VAL
        for (size_t i = 0; i < data_len; i++) {
            result |= (uint64_t)payload[i] << ((data_len - 1 - i) * 8);
        }
    }
    // 有符号值做符号扩展
    if (is_signed == SIGNED_VAL) {
        uint64_t sign_bit = 1ULL << (data_len * 8 - 1);
        if (result & sign_bit) {
            // 所有高位补1
            result |= ~((1ULL << (data_len * 8)) - 1);
        }
    }
    return result;
}

内容的提问来源于stack exchange,提问作者Salih MSA

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:06:17