我试图找出无符号溢出如何与减法一起工作,所以我编写了以下测试来试试:
#include<stdio.h>
#include<stdlib.h>
unsigned char minWrap(unsigned char a, unsigned char b) {
return a > b ? a - b : a + (0xff - b) + 1;
}
int main(int argc, char *argv[]) {
unsigned char a = 0x01, b = 0xff;
unsigned char c = a - b;
printf("0x%02x 0x%02x 0x%02x\n", a-b, c, minWrap(a,b));
return EXIT_SUCCESS;
}
作为输出:
0xffffff02 0x02 0x02
我原本预计输出会相同三次。我的问题是:添加/减去无符号字符并期望它们在0xff
处转换是否总是安全的?
或者更一般,使用uintN_t
进行计算并且期望结果是模2 ^ N是否安全?
答案 0 :(得分:2)
添加/减去无符号字符是否总是安全的,并期望它们在0xff处回绕?
没有。在C中,char
类型的对象通过通常的整数提升。因此,如果char
的范围符合int
(通常),则会转换为int
,否则为unsigned
。
a - b
- &gt; 0x01 - 0xFF
- &gt; 1 - 255
- &gt; -254
。
以下是未定义的行为,因为%x
与int
不匹配且-254的值不在unsigned
范围内(请参阅@EOF评论)。典型的行为是转换为unsigned
printf("0x%02x\n", a-b);
// 0xffffff02
使用
uintN_t
进行计算是否安全,并期望结果为模2 ^ N?
是。但请确保生成uintN_t
类型的结果,并避免意外的常规整数促销。
#include <inttypes.h>
uint8_t a = 0x01, b = 0xff;
uint8_t diff = a - b;
printf("0x%02x\n", (unsigned) diff);
printf("0x%02" PRTx8 "\n", diff);
答案 1 :(得分:1)
a-b
行中{p> printf
在a
和b
被提升为int
后进行评估。此外,由于您的运行时环境在格式说明符中使用了unsigned int
,因此该值被视为%x
。
它相当于:
int a1 = a;
int b1 = b;
int x = a1 - b1;
printf("0x%02x 0x%02x 0x%02x\n", x, c, minWrap(a,b));
C99标准的 6.3.1.8常规算术转换部分有更多细节。
理论上,当int
中预期unsigned int
时,使用printf
是未定义行为的原因。像您一样,宽松的运行时环境会将int
视为unsigned int
并继续打印该值。
答案 2 :(得分:1)
关于整数转换的标准的6.3.1.8/1:
对两个操作数执行整数提升。然后将以下规则应用于提升的操作数
如果两个操作数具有相同的类型,则不再进一步转换 需要的。
否则,如果两个操作数都有有符号整数类型或两者都有 无符号整数类型,操作数类型为较小的整数 转换等级转换为具有更大的操作数的类型 秩。
否则,如果具有无符号整数类型的操作数具有等级 那么,大于或等于另一个操作数的类型的等级 带有符号整数类型的操作数转换为 具有无符号整数类型的操作数。
否则,如果带有符号整数类型的操作数的类型可以 表示带有unsigned的操作数类型的所有值 整数类型,然后转换具有无符号整数类型的操作数 到带有符号整数类型的操作数的类型。
否则,两个操作数都将转换为无符号整数类型 对应于带有符号整数类型的操作数的类型。
在这种情况下,环绕很好地定义。在表达式a-b
中,因为两个操作数都是unsigned char
类型,所以它们首先被提升为int
并执行操作。如果将此值分配给unsigned char
,则会将其正确截断。但是,您使用printf
格式说明符将此值传递给%x
,该格式说明符需要unsigned int
。要正确显示,请使用需要%hhx
的{{1}}。