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

How did I get a value larger than 8 bits in size from an 8-bit integer?

本文关键字:8位 一个 大于 整数      更新时间:2023-10-16

我找到了藏在这颗小宝石后面的一个极其讨厌的虫子。我知道,根据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: %in", SCHAR_MIN);
   printf("SCHAR_MAX: %in", SCHAR_MAX);
   for (int32_t i = 0; i <= 300; i++)
      printf("c: %in", c--);
   printf("c: %in", 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上查看。

这是一个编译器错误。

虽然对未定义行为得到不可能的结果是一个有效的结果,但实际上在你的代码中没有未定义的行为。编译器认为行为是未定义的,并相应地优化。

如果c被定义为int8_t, int8_t升格为int,那么c--应该在int算法中执行c - 1的减法运算,并将结果转换回int8_tint中的减法不会溢出,并且将超出范围的整型值转换为另一种整型是有效的。如果目标类型是带符号的,则结果是实现定义的,但它必须是目标类型的有效值。(如果目标类型是无符号的,结果是定义良好的,但这里不适用。)

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

在这种情况下,它似乎是一个一致性错误。表达式c--应该以类似于c = c - 1的方式操作c。在这里,右边的c的值被提升为int类型,然后进行减法。因为cint8_t的范围内,所以这个减法不会溢出,但是它可能会产生一个超出int8_t范围的值。当赋值该值时,将转换回类型int8_t,因此结果将适合c。在超出范围的情况下,转换具有实现定义的值。但是超出int8_t范围的值不是有效的实现定义值。实现不能"定义"8位类型突然容纳9位或更多位。对于实现定义的值,表示产生int8_t范围内的值,程序继续运行。 C标准因此允许诸如饱和算术(在DSP上常见)或环绕(主流架构)之类的行为。

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

我不能在评论中容纳这个,所以我把它作为一个答案贴出来。

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

我测试了Ideone上发布的代码,并将c--替换为c = c - 1,值保持在[-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位。

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


如果将局部变量标记为volatile,则强制为其使用内存,从而获得范围内的期望值

汇编代码揭示了问题:

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

EBX应使用FF后减量,或仅使用BL,其余EBX清除。奇怪的是,它用的是潜艇而不是dec。它是300 &amp的逐位反转;255 = 44。-45 = ~44。

使用c = c - 1:

需要做更多的工作
mov eax, ebx
mov edi, OFFSET FLAT:.LC2   ;"c: %in"
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: %in"
mov esi, edx

所以这是优化器的一个bug。

%hhd代替%i !应该能解决你的问题。

你在这里看到的是编译器优化的结果,你告诉printf打印一个32位的数字,然后把一个(据说是8位的)数字压入堆栈,这实际上是指针大小,因为这就是x86中的push操作码的工作方式。

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

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

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

c本身定义为int8_t,但当在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值I变为300而c变为-300。最后一个值是因为

printf("c: %in", c);
相关文章: