添加两个uint8_t类型时出现转换警告

9

我有以下的C语言代码:

typedef unsigned char uint8_t;

void main(void) {
    uint8_t a = 1, b = 2, res;
    res = a + b;
}   

当我使用gcc -Wconversion编译此代码时,我会收到以下警告:

test.c: In function 'main':
test.c:5:10: warning: conversion to 'uint8_t' from 'int' may alter its value [-Wconversion]

能否有人解释一下为什么会出现这个警告?这三个变量都是uint8_t类型,所以我不太明白int从哪里来。


4
  • 运算符返回 int
- ikh
@ikh,使用```来编写代码。 - Shahbaz
@Shahbaz 哦,谢谢!我不知道这个。 - ikh
@ikh,请打开一个“提问”窗口并阅读右侧“如何格式化”下面的内容。 - Shahbaz
1
旁注:自C99以来,您可以使用<stdint.h>定义的uint8_t和其他类型。您可以使用它。然后使用gcc -std=c99gcc -std=gnu99进行编译(以获取gnu扩展)。如果在更高版本上,则gcc -std=c11gcc -std=gnu11也可以。 - Shahbaz
@Shahbaz,我知道 stdint.h,无论如何还是谢谢你。 - watain
3个回答

12

我不是很理解int从哪来。

int来源于C语言标准。在执行运算前,所有算术运算符的操作数都会被提升。在这种情况下,uint8_t会被提升为int,所以你需要使用强制转换来避免警告:

res = (uint8_t)(a + b);

以下是标准定义整数提升的方式:

6.3.1.1 如果一个 int 类型可以表示原始类型的所有值,则将其转换为 int;否则,将其转换为 unsigned int。这被称为整数提升。

由于 int 可以容纳 uint8_t 的所有可能值,所以在加法操作中, ab 都会被提升为 int 类型。


3
另外还有两件事情:1)在大多数处理器上,使用 uint8_t 进行数学运算需要更多的周期。2)显式转换可以提醒你溢出语义是什么。如果只是将两个 uint8_t 相加,则结果可能会大于 255,如果不将其强制转换回去的话。 - nmichaels
2
@watain 你不能强制gcc不进行int类型提升,因为那会违反标准。实际上,编译器可以优化生成的代码,如果它知道只有最后8位将被使用,则不进行值到int类型的提升。然而,代码必须表现得好像已经进行了提升,包括警告。如果你需要经常将变量强制转换回uint8_t类型,你可能想要定义一个宏,比如 #define TO_UINT8(x) ((uint8_t)(x))。 - Sergey Kalinichenko
1
@JosephQuinsey 当然可以!非常感谢您提醒我为什么讨厌宏。 - Sergey Kalinichenko
1
EricPostpischil:是的,比 int 更快。这是因为编译器必须发出额外的指令,以确保寄存器只保存8位。这些指令在算术运算之后,在加载之后和存储之前被抛入。许多系统甚至不允许您从内存中读取单个字节。无论如何,它因处理器而异,在嵌入式设备中更为重要,但通常是正确的。这就是为什么 C 有 uint_fast8_t 的原因。 - nmichaels
1
@nmichaels:使用uint_8进行算术运算通常不需要额外的指令来确保寄存器仅包含八位。只需正常进行算术运算并忽略额外的位即可。这适用于加法、减法、乘法、按位AND、OR、XOR和NOT以及左移操作。除法和右移操作可能需要更多的注意。而且你不能假设int是安全的,因为我们看到当前实现中有32位int在64位寄存器中的情况。除非你针对特定的机器,否则最好编写适合你的数据类型,而不是猜测它们。 - Eric Postpischil
显示剩余7条评论

4

除了现有答案中关于整数提升的解释外,值得解释一下 -Wconversion 在警告你什么。

由于 ab 都是 uint8_t 类型,a + b 的结果可能无法适应另一个 uint8_t。通过将结果赋回到 uint8_t 中,你强制编译器执行转换,这可能会更改值。因此,变量 res 可能并不实际代表 a + b 的实际值。

例如,如果 ab 都是 0xff,那么:

  • a + b0x1fe,类型为 int
  • (uint8_t)(a + b)0xfe

但是 a + b 的结果也可能不适合于 intunsigned int。那么为什么使用 intunsigned int 时,gcc没有发出警告呢? - watain
这不是真的。在任何类型上进行数学运算都可能导致溢出。 - phuclv
@watain,uint8_t 的宽度恰好为 8 位,但是 int 必须至少有 16 位宽度(为了满足 <limits.h>INT_MAX 值的标准规则)。一个 8 位数字的最大值的两倍可以放在 9 位中 - 因此它总是可以在 int 中不溢出地存储。 - benj

0
你应该更多地了解C语言的int promotion rules。这是规则,编译器必须遵守。没有它,一些“琐碎”的代码将无法正常工作(就像你预期的那样)。例如:
char d[4] = {0xFF, 0xFE, 0x80, 40};
int i = (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];
int j = d[2] - d[0]*d[1];

在一些8位微控制器的编译器中,有一个选项可以禁用C标准符合性以加快数学运算速度,因为始终将char提升为int来进行数学运算可能会导致不必要的操作并减慢进程。它还会消耗更多内存,在这种嵌入式系统中非常宝贵。当然,这将使一些代码不可移植或根本无法工作,但这是一种权衡。
现代微处理器是16位或更高,因此使用char不会使您免受任何操作的影响,但由于每个操作之前/之后的符号调整,可能会增加代码大小和速度。大多数RISC架构甚至没有用于操作与其本机大小不同的操作数的指令,除了加载/存储和符号扩展。因此,建议对所有临时变量使用本机int大小。小于本机寄存器大小的类型应仅在存在非常大的数组可能会增加缓存未命中,以保存或读取来自某些其他类型的内存或与其他系统/库兼容性的情况下使用。

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