为什么对一个1字节变量进行按位取反会返回一个4字节的值?

3

让我们以以下两个1字节变量为例:

uint8_t x1 = 0x00;
uint8_t x2 = 0xFF;

打印位补时,结果是一个4字节的变量:

printf("%02X -> %02X; %02X -> %02X\n", x1, ~x1, x2, ~x2);
00 -> FFFFFFFF; FF -> FFFFFF00

我知道这可以通过转换或屏蔽解决:
printf("%02X -> %02X; %02X -> %02X\n", x1, (uint8_t) ~x1, x2, (uint8_t) ~x2);
00 -> FF; FF -> 00
printf("%02X -> %02X; %02X -> %02X\n", x1, ~x1&0xFF, x2, ~x2&0xFF);
00 -> FF; FF -> 00

但是为什么会出现这种非直观的行为呢?

1
因为%X是用于unsigned int的。而且,uint8_t不是一个2字节的变量。 - Eugene Sh.
2
查找“C语言中的整数提升”以了解正在发生的事情。 - Sergey Kalinichenko
2个回答

2
许多计算机处理器在大部分操作中都有一个“字”大小。例如,在32位机器上,可能有一条指令加载32位,一条指令存储32位,一条指令将一个32位数字加到另一个数字上等等。
在这些处理器上,使用其他大小可能会很麻烦。可能没有乘以16位数字的指令。C是在这些机器上发展起来的。它被设计成int(或unsigned int)是“适合你运行的机器的任何大小”,而charshort则适合于在内存中存储东西,但一旦从内存加载到处理器寄存器中,C会像它们是int一样处理它们。
这简化了早期C编译器的开发。编译器不必通过执行32位补码指令后跟AND指令来删除不需要的高位来实现您的补码。它只执行一个普通的32位补码。
今天我们可以以不同的方式开发语言,但C背负着这个遗产。

即使是现代处理器也无法处理任意大小的单词。据我所知,ARM具有16位乘法(无法从C中使用),但没有8位乘法。对于16位和8位加法、减法和其他操作也是如此。所有操作都必须提升为32位值。英特尔是唯一处理不同大小值的架构。因此,这不是遗留问题,而是处理器通常的构建方式。 - user1143634

1
当您将~运算符应用于x1x2时,首先会对值进行整数提升,因为uint8_tint小。然后将运算符应用于提升的值。
因此,~x1实际上是~0x00000000(即0xFFFFFFFF),~x2实际上是~0x000000FF(即FFFFFF00)。这就是您得到的值的原因。
另外,%x格式说明符需要一个unsigned int,它会打印出这样的内容。
您需要使用%hhx作为格式说明符。 这表示一个unsigned char参数。
printf("%02hhX -> %02hhX; %02hhX -> %02hhX\n", x1, ~x1, x2, ~x2);

hhX 打印 00 -> FFFF; FF -> FF00 - Sparkler
你确定你正在使用 hhX 而不是 hX 吗? - dbush
@chux 在这种情况下实际上可能没有帮助。我的实现将PRIx8定义为"x" - dbush
@dbush,你是对的,显然在我的系统上,hhX会产生一个“格式有太多参数[-Wformat-extra-args]”的警告。 - Sparkler
PRIx8对于实现来说是可以使用"x"的。当它打印一个uint8_t时,这不会有问题。但是,这个答案尝试使用printf("%02hhX\n", some_int_with_negative_value);做一些不同的事情,这是未定义行为 - 我想。 - chux - Reinstate Monica
你的代码导致未定义行为。%hhX 必须与 signed charunsigned char 类型的参数匹配。 - M.M

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