1 背景
工作中需要向下游规划(Planning)模块发送视觉检测到的车道线线型与颜色数据,但软件总体使用的是旧有架构,只有uint8_t
的线型结构没有颜色接口。
2 问题
实际上,颜色对应一个最大取值为3的枚举,线型对应一个最大取值为34的枚举,自然而然想到是使用线型接口的低六位存储线型值,使用线型接口的高两位存储颜色值,下游使用时,直接将线型接口位与0x3f
获取真实线型值,将线型接口右移6位获取真实颜色值:1
2
3uint8_t line_type_interface = 0b11100010;
uint8_t line_type = line_type_interface & 0x3f;
uint8_t line_color = line_type_interface >> 6;
这在iECU(intelligent ECU) RTE(Run-Time Environment,AUTOSAR架构中VFB的接口实现)层上的表现是顺理成章的。
问题出现在离线调试阶段。我们的调试手段是将iECU中的运行时RTE数据转换为LCM数据并通过LCM UDP组播的形式向外发送,通过x86的日志上位机实时接收并存储LCM UDP报文,离线调试时将存储的LCM日志文件进行回放。RTE的线型接口为uint8_t
类型,但LCM消息中对应的线型接口被定义成了int8_t
类型。
我在为LCM消息赋值时,直接将RTE的线型接口的值赋给了LCM消息中的对应接口,这就造成了uint8_t
向int8_t
的隐式类型转换,我没有在意这一点,下游规划模块同样没有在意这一点,下游模块离线调试时直接将LCM日志文件中int8_t
线型接口值做了向uint8_t
的显式强制类型转换,然后进行了线型值和颜色值的提取操作,并且没有出过问题,直到同样需要接收相同信息的HMI(Human Machine Interface)下游模块遇到了问题:他在右移提取颜色值时出现了异常大的值。原来,HMI模块在进行颜色值的提取操作时直接将LCM日志中的接口值进行了右移操作,而LCM消息中对应的接口被定义成了int8_t
类型,当接口取值为形如0b11100010
的符号位为1的值时,右移操作会将空出位补1,从而造成提取的颜色数值异常。
当然,像规划模块那样,先将int8_t
值向uint8_t
做强制类型转换,再进行右移提取操作就不会有问题了,但这引发了我的思考:从原始RTE数据到离线注入数据,发生了uint8_t
-> int8_t
-> uint8_t
的两次类型转换,为什么没有问题?我隐约地觉得应该会有问题。HMI模块还提了一个很基础但具有迷惑性的问题:当LCM日志中接口数据为0b10000000
时,它对应的十进制是什么-0
吗?(答案:-128。int8_t
的取值范围是[-128, 127],0b10000000
即int8_t
的取值下限。)
3 知识点
- 在计算机系统中,数值一律用补码来表示(存储),在程序调试时,所观察到的变量的十六进制均代表变量的补码。使用补码主要原因是可以将符号位和其它位统一处理;同时,减法也可按加法来处理;
- 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的补码为该数绝对值求反码(原码按位取反)后整体加1;
- 所谓有符号数与无符号数间的类型转换,数据在内存中的补码表示并未发生改变,改变的是该补码在类型转换前后所对应的十进制含义,即计算机理解内存上补码数据的方式。例如,当RTE原始数据(
uint8_t
)取值为0b11100010
时,计算机认为其代表十进制为:此时,数据在内存中的补码为11100010
。做了向int8_t
的类型转换后,数据在内存中的补码依然是11100010
,但此时计算机认为其代表十进制为:即:1
-~(0b1100010 - 1) = -0b0011110
- 当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数(《C++ Primer》),反之亦然(博主注)。注意到:$2^8$是
uint8_t
和int8_t
类型数据的模。