在C语言中的类型转换:注意事项

3
我正在使用C语言编程Atmel SAMD20。我遇到了一个错误,现在已经修复了,但我不太确定为什么会出错。有人能指出来吗?(可能太明显了,我等下要自己面壁思过了。)
一组传感器正在生成uint16_t数据,我将其转换为uint8_t以通过I2C发送。所以,这就是我最初的写法:
for (i = 0; i < SENSBUS1_COUNT; ++i)
{
  write_buffer[ (i*2) ] = (uint8_t) sample_sensbus1[i] & 0xff;
  write_buffer[(i*2)+1] = (uint8_t) sample_sensbus1[i] >> 8;
}

这里,write_bufferuint8_t 类型,而 sample_sensbus1uint16_t 类型。

出于某种原因,这会导致最高有效字节出错(在大多数情况下,最高有效字节只是 1(即 0x100))。另一方面,这段代码运行良好,正是所期望的:

for (i = 0; i < SENSBUS1_COUNT; ++i)
{
  write_buffer[ (i*2) ] = sample_sensbus1[i] & 0xff;
  write_buffer[(i*2)+1] = sample_sensbus1[i] >> 8;
}

显然,隐式转换比我更聪明。

发生了什么?


2
故事的寓意:括号!扇自己一巴掌 - Shreyas
2
而咖啡..... - Shreyas
@ChristianGibbons 被截断的部分也可能是依赖于系统的。很难说。也许在 大端 系统上,较低的字节被截断了。不要引用我说的话,但这很可能是真的。 - Shreyas
1
假设您创建了一个指向uint16_t数组的uint8_t *,那么它们被截断就不会有问题,因为您可以使用字节索引来索引整个数据集(uint8_t数组中的元素数量将是原数组的两倍)。但正如您提到的字节序一样,这确实提醒我字节顺序实际上取决于系统。 - Christian Gibbons
1
@ShreyasVinod 不,C语言比那更聪明。如果你在大端系统上将uint16_t强制转换为uint8_t,你仍然会截断更重要的字节。但是,如果你将指针——即uint16_t *强制转换为uint8_t *,在大端系统上解引用时确实会丢失较不重要的字节。 - Charles Srstka
显示剩余2条评论
5个回答

4
write_buffer[(i*2)+1] = (uint8_t) sample_sensbus1[i] >> 8;

这相当于:
write_buffer[(i*2)+1] = ((uint8_t) sample_sensbus1[i]) >> 8;

正如您所看到的,它在移位之前进行了强制类型转换。 您最重要的字节现在已经消失了。

尽管如此,这应该可以工作:

write_buffer[(i*2)+1] = (uint8_t) (sample_sensbus1[i] >> 8);

4

在执行移位或掩码操作之前,您的代码将 uint16_t 转换为 uint8_t。它被视为您编写的以下代码:

write_buffer[ (i*2) ] = ((uint8_t)sample_sensbus1[i]) & 0xff;
write_buffer[(i*2)+1] = ((uint8_t)sample_sensbus1[i]) >> 8;

您可能需要:

write_buffer[ (i*2) ] = (uint8_t)(sample_sensbus1[i] & 0xff);
write_buffer[(i*2)+1] = (uint8_t)(sample_sensbus1[i] >> 8);

实际上,未转换版本也可以。请记住,强制转换告诉编译器“我比你更了解这个;按照我的说法做”。如果您不比编译器更了解,则可能会很危险。尽可能避免使用强制转换。您还需要注意的是,左移或右移类型大小的位数(或更多)是未定义的行为。但是,((uint8_t)sample_sensbus [i]) >> 8不是未定义的行为,因为存在“通常的算术转换”,这意味着在移位发生之前,(uint8_t)sample_sensbus[i]的结果将被转换为int,而int的大小不能为8位(必须至少为16位才能满足标准),因此移位不会太大。

3

Casting是一元前缀运算符,因此其优先级非常高。

(uint8_t) sample_sensbus1[i] & 0xff

parses as

((uint8_t)sample_sensbus1[i]) & 0xff

在这种情况下,& 0xff 是多余的。但是:
(uint8_t) sample_sensbus1[i] >> 8

parses as

((uint8_t)sample_sensbus1[i]) >> 8

这里的cast将数字截断为8位,然后>> 8移位使所有内容都被移出。


3
问题出在这个表达式中:
(uint8_t) sample_sensbus1[i] >> 8; 

它正在执行以下序列:

  1. sample_sensbus1[i]转换为uint8_t,即将其截断为8个最低有效位。这就是你失去数据的地方。
  2. 将上述结果作为通常算术转换的一部分转换为int,使得只设置了8个较低位的int。
  3. 将上述int向右移动8位,实际上使整个表达式为零。

3
这是一个运算符优先级的问题。在第一个例子中,您首先将其转换为uint8_t,然后再应用&>>运算符。在第二个例子中,在隐式转换发生之前就先应用了这些运算符。

1
该死的括号丢了。我就知道。扶额 - Shreyas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接