我如何从一个8位整数得到一个大于8位的值?

我找到了藏在这个小宝石后面的一个极其恶心的虫子。我知道,根据 C + + 规范,有符号溢出是未定义行为的,但只有当溢出发生时,该值扩展到位宽 sizeof(int)。根据我的理解,增加一个 abc1的未定义行为不应该像增加一个 abc2那样长。但这并不能解释 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   // <= ..........

Check it out on ideone.

7382 次浏览

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


如果将局部变量标记为 volatile,则必须为其使用内存,并因此获得范围内的预期值。

我不能把这个放在评论里,所以我把它作为一个答案发布出去。

由于一些非常奇怪的原因,--操作符恰好是罪魁祸首。

我测试了 Ideone 上发布的代码,并用 c = c - 1替换了 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--这样的表达式。它很可能将返回值提升到 int并传递给它。这是我能得出的唯一合乎逻辑的结论,因为你实际上得到的值不能适合8位。

这是一个编译器错误。

Although getting impossible results for undefined behaviour is a valid consequence, there is actually no undefined behaviour in your code. What's happening is that the compiler thinks the behaviour is undefined, and optimises accordingly.

If c is defined as int8_t, and int8_t promotes to int, then c-- is supposed to perform the subtraction c - 1 in int arithmetic and convert the result back to int8_t. The subtraction in int does not overflow, and converting out-of-range integral values to another integral type is valid. If the destination type is signed, the result is implementation-defined, but it must be a valid value for the destination type. (And if the destination type is unsigned, the result is well-defined, but that does not apply here.)

编译器可能有除了不符合标准之外的错误,因为还有其他要求。编译器应该与自身的其他版本兼容。也可以期望它在某些方面与其他编译器兼容,并且也符合其大多数用户基础所持有的关于行为的某些信念。

在这种情况下,它似乎是一个一致性错误。表达 c--应该以类似于 c = c - 1的方式操纵 c。在这里,右边的 c的值被提升为 int类型,然后进行减法。因为 cint8_t的范围内,这个减法不会溢出,但是它可能产生一个超出 int8_t范围的值。当这个值被赋值时,转换发生回到类型 int8_t,所以结果适合回到 c。在超出范围的情况下,转换具有实现定义的值。c2对于实现定义的值意味着在 int8_t范围内产生某些东西,并且程序继续。c3

编译器在操作小整数类型(如 int8_tchar)的值时使用更广泛的底层机器类型。当算法执行时,超出小整数类型范围的结果可以可靠地捕获在这个更广泛的类型。为了保持变量为8位类型这一外部可见的行为,必须将较宽的结果截断到8位范围内。这需要显式代码,因为机器存储位置(寄存器)比8位更宽,并且值更大。这里,编译器将 忽略了正常化的值直接传递给 printfprintf中的转换说明符 %i不知道参数最初来自于 int8_t计算; 它只是使用 int参数。

我认为这是通过优化代码实现的:

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

编译器对 ic都使用 int32_t i变量。关闭优化或使直接铸造 printf("c: %i\n", (int8_t)c--);

Use %hhd instead of %i! Should solve your problem.

What you see there is the result of compiler optimizations combined with you telling printf to print a 32bit number and then pushing a (supposedly 8bit) number onto the stack, which is really pointer sized, because this is how the push opcode in x86 works.

I think it happened because your loop will go until the int i will become 300 and c become -300. And the last value is because

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

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

: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

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

c is itself defined as int8_t, but when operating ++ or -- over int8_t it is implicitly converted first to int and the 操作的结果 instead C 的内值 is printed with printf which happens to be int.

参见 c实际价值在整个循环之后,特别是 在最后一次减刑之后

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

它的正确值类似于行为 -128 + 1 = 127

c starts to use int size memory but printed as int8_t when printed as itself using only 8 bits. Utilizes all 32 bits when used as int

[编译器错误]