我如何从8位整数获得大于8位的值?

我追踪了这个小gem背后的一个非常讨厌的虫子。 我知道,根据C ++规范,有符号溢出是未定义的行为,但只有当值扩展到位宽sizeof(int)时发生溢出。 据我了解,只要sizeof(char) < sizeof(int) ,增加一个char不应该是未定义的行为。 但是这并不能解释c如何获得不可能的价值。 作为一个8位整数, c如何保持大于其位宽的值?

 // Compiled with gcc-4.7.2 #include <cstdio> #include <stdint.h> #include <climits> int main() { int8_t c = 0; printf("SCHAR_MIN: %i\n", SCHAR_MIN); printf("SCHAR_MAX: %i\n", SCHAR_MAX); for (int32_t i = 0; i <= 300; i++) printf("c: %i\n", c--); printf("c: %i\n", c); return 0; } 

产量

 SCHAR_MIN: -128 SCHAR_MAX: 127 c: 0 c: -1 c: -2 c: -3 ... c: -127 c: -128 // <= The next value should still be an 8-bit value. c: -129 // <= What? That's more than 8 bits! c: -130 // <= Uh... c: -131 ... c: -297 c: -298 // <= Getting ridiculous now. c: -299 c: -300 c: -45 // <= .......... 

检查一下ideone。

这是一个编译器错误。

尽pipe为未定义的行为获取不可能的结果是一个有效的结果,但在代码中实际上没有未定义的行为。 发生什么事是编译器认为行为是不确定的,并相应地进行优化。

如果c被定义为int8_t ,并且int8_t int ,那么c--应该在int算术中执行减法c - 1并将结果转换回int8_tint中的相减不会溢出,并且将超出范围的整数值转换为另一个整数types是有效的。 如果目标types已签名,则结果是实现定义的,但是它必须是目标types的有效值。 (如果目标types是无符号的,结果是明确的,但在这里不适用。)

一个编译器可能会有不符合标准的错误,因为还有其他的要求。 编译器应该与其他版本兼容。 它也可能在某些方面与其他编译器兼容,也符合一些关于大多数用户群所持有的行为的信念。

在这种情况下,它似乎是一个符合性错误。 expression式c--应该以类似于c = c - 1的方式操作c 。 在这里,右边的c的值被提升为inttypes,然后进行相减。 由于cint8_t的范围内, int8_t这个减法不会溢出,但是可能会产生一个超出int8_t范围的int8_t 。 当这个值被赋值时,转换返回到int8_ttypes,所以结果适合c 。 在超出范围的情况下,转换具有实现定义的值。 但是, int8_t范围int8_t值不是有效的实现定义的值。 一个实现不能“定义”8位types突然保持9位或更多位。 对于实现定义的值意味着int8_t范围内的int8_t被生成,并且程序继续。 因此,C标准允许诸如饱和算术(在DSP上常见)或环绕(主stream架构)等行为。

编译器在处理int8_tchar等小整数types的值时使用更宽的底层机器types。 当执行算术运算时,在这个更宽的types中可以可靠地捕获超出小整数types范围的结果。 为了保持variables是8位types的外部可见行为,更宽的结果必须被截断为8位范围。 由于机器存储位置(寄存器)的宽度大于8位,所以需要使用显式代码,并且对于较大的值来说是满意的。 在这里,编译器忽略了规范化的值,只是简单地把它传递给printfprintf的转换说明符%i不知道该参数最初来自int8_t计算; 它只是与一个int参数一起工作。

我不能评论这个,所以我把它作为答案。

出于某种非常奇怪的原因,这个--操作员碰巧是罪魁祸首。

我testing了在Ideone上发布的代码,并用c = c - 1replace了c--值保持在[-128 … 127]范围内:

 c: -123 c: -124 c: -125 c: -126 c: -127 c: -128 // about to overflow c: 127 // woop c: 126 c: 125 c: 124 c: 123 c: 122 

怪异的眼睛? 我不太了解编译器对像i++i--这样的expression式。 这很可能会将返回值提升为一个int并传递给它。 这是我能想出的唯一合乎逻辑的结论,因为你实际上正在获得不能适应8位的价值。

我猜测底层硬件仍然使用32位寄存器来保存int8_t。 由于规范没有强制执行溢出行为,因此实现不会检查溢出,并允许存储更大的值。


如果将局部variables标记为volatilevariables,则强制使用内存,从而获得范围内的期望值。

汇编代码揭示了这个问题:

 :loop mov esi, ebx xor eax, eax mov edi, OFFSET FLAT:.LC2 ;"c: %i\n" sub ebx, 1 call printf cmp ebx, -301 jne loop mov esi, -45 mov edi, OFFSET FLAT:.LC2 ;"c: %i\n" xor eax, eax call printf 

EBX应该用FF后减,或者只有BL应该和EBX的其余部分一起使用。 好奇它使用sub而不是dec。 -45是平淡的神秘。 这是300和255 = 44. -45 =〜44的位反转。 有一个连接的地方。

它使用c = c – 1来完成更多的工作:

 mov eax, ebx mov edi, OFFSET FLAT:.LC2 ;"c: %i\n" add ebx, 1 not eax movsx ebp, al ;uses only the lower 8 bits xor eax, eax mov esi, ebp 

然后它只使用RAX的低部分,所以它被限制在-128到127之间。编译器选项“-g -O2”。

没有优化,它会产生正确的代码:

 movzx eax, BYTE PTR [rbp-1] sub eax, 1 mov BYTE PTR [rbp-1], al movsx edx, BYTE PTR [rbp-1] mov eax, OFFSET FLAT:.LC2 ;"c: %i\n" mov esi, edx 

所以这是优化器中的一个错误。

使用%hhd而不是%i ! 应该解决你的问题。

你看到的是编译器优化的结果,你告诉printf打印一个32位的数字,然后将一个(应该是8位)的数字推到堆栈上,这实际上是指针大小,因为这是x86中的推式操作码的工作原理。

我认为这是通过优化代码来完成的:

 for (int32_t i = 0; i <= 300; i++) printf("c: %i\n", c--); 

编译器为ic使用int32_t ivariables。 closures优化或直接投射printf("c: %i\n", (int8_t)c--);

c本身被定义为int8_t ,但是当操作++或者-- over int8_t它首先被隐式地转换为int而操作结果 是c的内部值用printf来打印,后者碰巧是int

查看整个循环 c实际值 ,特别是在最后一次递减之后

 -301 + 256 = -45 (since it revolved entire 8 bit range once) 

其正确的价值类似的行为-128 + 1 = 127

c开始使用int大小的内存,但打印时只用8 bits打印为int8_t 。 作为int使用时,全部使用32 bits

[编译器错误]

我认为这是因为你的循环会一直持续下去,直到int变成300,c变成-300。 最后一个值是因为

 printf("c: %i\n", c);