作为一个RT-Thread开发者,回顾这一段时间以来趟过的坑(1)
1: -1不等于-1的情况
Q:-1在什么情况下等于-1呢?-1当然在任何情况下等于-1呀,生活中当然这么回答没有任何问题,因为 -1在人看来是独一无二的。
场景:
前一段时间在给某个BSP适配串口驱动时,需要传递一个int型的变量,由于个人主观意识,在最初的环节将这个变量设置为了char型,在小伙伴的帮助下排查了好久最终定位到了数据类型不匹配的原因,直接上代码,这里以RT1060 BSP 举例。
static int imxrt_getc(struct rt_serial_device *serial)
{
int ch;
struct imxrt_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct imxrt_uart, serial);
ch = -1;
if (LPUART_GetStatusFlags(uart->uart_base) & kLPUART_RxDataRegFullFlag)
{
ch = LPUART_ReadByte(uart->uart_base);
}
return ch;
}
static int imxrt_getc(struct rt_serial_device *serial)
{
char ch;
struct imxrt_uart *uart;
RT_ASSERT(serial != RT_NULL);
uart = rt_container_of(serial, struct imxrt_uart, serial);
ch = -1;
if (LPUART_GetStatusFlags(uart->uart_base) & kLPUART_RxDataRegFullFlag)
{
ch = LPUART_ReadByte(uart->uart_base);
}
return ch;
}
仅一个数据类型不一致,整个代码就跑飞了,跑飞的地方如下:
void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
{
switch (event & 0xff)
{
case RT_SERIAL_EVENT_RX_IND:
{
int ch = -1;
rt_base_t level;
struct rt_serial_rx_fifo* rx_fifo;
/* interrupt mode receive */
rx_fifo = (struct rt_serial_rx_fifo*)serial->serial_rx;
RT_ASSERT(rx_fifo != RT_NULL);
while (1)
{
ch = serial->ops->getc(serial);
if (ch == -1) break; //问题关键
/* disable interrupt */
level = rt_hw_interrupt_disable();
rx_fifo->buffer[rx_fifo->put_index] = ch;
rx_fifo->put_index += 1;
if (rx_fifo->put_index >= serial->config.bufsz) rx_fifo->put_index = 0;
...
}
}
}
}
我们可以看到者关键的一句if (ch == -1) break;这里在无输入时ch会返回-1并退出循环。
通常上述C代码在翻译成汇编时,会翻译成 判断 ch - 1的结果的相应指令。这里ch是char型的-1,在内存中存储的值就是0xFF,
这里的--1是个立即数,在这里回被默认为32位int型,所以这里-1在内存中的值为0xFFFFFFFF,通过运算单元0XFF-0xFFFFFFFF比较二者的大小,结果显而易见不为0,这样就出现了人为认为的-1 = -1而机器人认为-1 ≠-1的现象,所以串口驱动便会卡死在这里。
2:-1 不等于 -1的情况2
场景:
在验证原子指令时出现了这么一个情况,编写测试用例时一致不通过,最终添加判断,发现 -1 > -1,这是受过九年义务教育的我能接受的?上代码:
出问题的地方:
base = 2;
result = rt_atomic_add(&base, -3);
uassert_true(base == -1);
这里uassert_true(base == -1);一直不通过。
中间省略长时间的调试过程。。。我们看一下rt_atomic_add这个函数:
rt_atomic_t rt_hw_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val)
{
rt_atomic_t result;
asm volatile ("amoadd.w %0, %1, (%2)" : "=r"(result) : "r"(val), "r"(ptr) : "memory");
return result;
}
看着没毛病吧,其实就是没毛病,错的时其没用对地方。该指令的语义是,将一个32位的val与ptr地址指向的数据相加并将结果保存在ptr所指向的内存空间。
函数在翻译成汇编时,会添加一些上下文代码,在RV64的qemu上使用上述指令测试时,由于其是64位环境,这个指令是操作32位数,而且结果是负数,这就很有趣了,工具链会将过程优化,把计算出来的32位有符号的-1优化为64为的有符号-1,64位-1在内存中的值为
0XFFFFFFFFFFFFFFFF,而与之相比较的-1默认为int型,在内存中的值位0XFFFFFFFF,上文提到,判断俩个数是否相等通常是通过做差,显然0XFFFFFFFFFFFFFFFF-0XFFFFFFFF 不等于 0,这样uassert_true(base == -1);便判断为不相等,且是前者大于后者。
3 未完待续
好文
有意思,mark
数据类型的坑 mark
理性探讨哈。错误函数imxrt_getc在返回的时候,char类型不会自动转换成int类型吗?我在windows上试着写了一个简单的例子,似乎不会出现这种-1!=-1的现象。
这个和编译环境也有关系,在WINDOWS上的开发环境测是测不出来的,会自动优化为我们所理解的 -1,而在单片机上跑,其资源小就不会有这样的优化,所以其会在寄存器层面判断这个值,所以会存在这样的情况@BruceTan
👍 一言不合就上代码
Mark一下
@牧尘
你用的哪个单片机,什么编译环境?
大家都想知道是哪个编译器返回的变量类型替代了返回值的类型。
RT1060 GCC@出出啊
这个总结相当不错,估计很多人遇到类似情况
在CH32V307 STM32L475上验证还是同样结果,证明这部分大家的处理方式都是一致的 若这么写是有问题的
相当不错的总结啊
@牧尘
感谢博主回复,确实是这样子。
我在STM32F407、GD32F407、MDK5环境下进行测试,就会出现问题了,确实是个大坑
MCU对这个数据没有优化,读出来是啥就是啥,不用MCU跑就跑不出上述情况@BruceTan
return a会被符号扩展,出问题只能说是编译器BUG@BruceTan
关于数据类型这一块,如果不能结果硬件的单元存储,是很容易让开发者陷入迷惑的。有时候,因为数据类型导致程序运行异常,而开发者却很难找到逻辑上的错误,那么数据类型的问题,往往就是主要原因。理解数据类型的时候,一定要结合硬件上的物理存储方式来加深理解。这是很多初学者容易忽视的地方。比如对unsigned char型数据,255+1的结果,不是256,而是0。
👍 👍
1那种情况是死循环,或错误的死循环。不是跑飞。
Mark一下
好坑,插个眼先
第一种情况昨天就出现了😂,我还在想咋会出现ff
看了这篇帖子成功避坑
期待后续的更新!
这太折磨人了吧
不错,mark一下
受教了
我的感受是,测试岗位真的有用
比较中必须使用同类型数据,if语句的参数必须是布尔类型, 这是基本的C语言编程素养
针对问题一,补充一点:
在IAR中char既可以是unsigned char也可以是signed char,建议写全,不然容易出现数据类型的错误逻辑判断。
设置如下:
相当不错的总结啊 mark
留意一下此种问题
不错
讲道理,根据RISC-V规范,-1这些如果是用bge做比较时是需要进行符号位扩展的,最大的可能是编译器编译成了bgeu这种无符号类型的,还有减法那个也是
第一种情况,signed char和unsigned char这两个与char本质是不一样的,前两者是整形,后者是字符型,标准只规定了char要足够容纳基本字符的表示就行了,并没有规定plain char的符号类型,而是implementation defined,因此这完全取决于编译器怎么做,所以用字符型与整型做比较本身就是UB。
第二种情况,你比较的是base和-1,而不是result和-1,根据标准来说,base == -1这个表达式是会做类型提升的,这里看不到base是什么类型的变量,也不知道rt_atomic_t是什么类型的,不多做评价,但我可以感觉到你后面的解析仍然没有抓住问题的根本
mark一下,对于第一种情况如果不动函数imxrt_getc里面ch的格式的话,char型的-1强转到int型进行临时的运算也是一种解决方案吧?死循环里面先强转ch到一个数再去和整数格式的-1比较
共享经验,可以让别人少走弯路。赞!
类型隐式转换
期待更新
期待更新
期待
keil里char默认也是unsigned的,像问题一这样用的话,改成signed char ch就可以了
建议用 C 标准库 stdint.h, 固定宽度的整数类型。C 编译器兼容,char 默认的数据符号位都有区别。
期待更新
这个总结相当不错,希望持续更新
mark
厉害
厉害呀大佬
虽然不确定,但是赋值时char隐式类型转换为int应该很早就成为了c标准,所以不应该说是单片机资源的问题,应该是编译器适配出问题了,没有符合c的标准@药RV
对于第一种情况,编译器不符合标准没有做类型转换的可能性还是比较低的。结合评论区其它网友说的情况来看,应该是IDE默认设置了char为unsigned char类型,所以在return的时候它的值没有根据signed类型整型变量隐式类型转换的规则进行转换,而是直接在高位补零。
mark
-1
的判断,下意识的去转成+1
操作就可以避免了