带有减法的无符号字符溢出

时间:2015-09-16 15:56:41

标签: c gcc

我试图找出无符号溢出如何与减法一起工作,所以我编写了以下测试来试试:

#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是否安全?

3 个答案:

答案 0 :(得分:2)

  

添加/减去无符号字符是否总是安全的,并期望它们在0xff处回绕?

没有。在C中,char类型的对象通过通常的整数提升。因此,如果char的范围符合int(通常),则会转换为int,否则为unsigned

a - b - &gt; 0x01 - 0xFF - &gt; 1 - 255 - &gt; -254

以下是未定义的行为,因为%xint不匹配且-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> printfab被提升为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}}。