我如何从一个8位整数中得到一个大于8位的值
How did I get a value larger than 8 bits in size from an 8-bit integer?
我找到了藏在这颗小宝石后面的一个极其讨厌的虫子。我知道,根据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上查看。
// 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 // <= ..........
这是一个编译器错误。
虽然对未定义行为得到不可能的结果是一个有效的结果,但实际上在你的代码中没有未定义的行为。编译器认为行为是未定义的,并相应地优化。
如果c
被定义为int8_t
, int8_t
升格为int
,那么c--
应该在int
算法中执行c - 1
的减法运算,并将结果转换回int8_t
。int
中的减法不会溢出,并且将超出范围的整型值转换为另一种整型是有效的。如果目标类型是带符号的,则结果是实现定义的,但它必须是目标类型的有效值。(如果目标类型是无符号的,结果是定义良好的,但这里不适用。)
编译器可能有除不符合标准之外的错误,因为有其他要求。编译器应该与自身的其他版本兼容。它也可能被期望在某些方面与其他编译器兼容,并且也符合其大多数用户群所持有的一些行为信念。
在这种情况下,它似乎是一个一致性错误。表达式c--
应该以类似于c = c - 1
的方式操作c
。在这里,右边的c
的值被提升为int
类型,然后进行减法。因为c
在int8_t
的范围内,所以这个减法不会溢出,但是它可能会产生一个超出int8_t
范围的值。当赋值该值时,将转换回类型int8_t
,因此结果将适合c
。在超出范围的情况下,转换具有实现定义的值。但是超出int8_t
范围的值不是有效的实现定义值。实现不能"定义"8位类型突然容纳9位或更多位。对于实现定义的值,表示产生int8_t
范围内的值,程序继续运行。 C标准因此允许诸如饱和算术(在DSP上常见)或环绕(主流架构)之类的行为。
当操作小整数类型(如int8_t
或char
)的值时,编译器使用更广泛的底层机器类型。当执行算术运算时,超出小整数类型范围的结果可以在这个更宽的类型中可靠地捕获。为了保持变量是8位类型的外部可见行为,必须将较宽的结果截断为8位范围。需要显式代码来做到这一点,因为机器存储位置(寄存器)的宽度大于8位,并且对较大的值感到满意。在这里,编译器忽略了对的值进行规范化,只是将其原样传递给printf
。printf
中的转换说明符%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 &的逐位反转;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--);
编译器对i
和c
都使用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);
- 如何获取一个数字的前3位
- 如何为位集找到/实现一个好的哈希函数
- 我的 cout 上有一个奇怪的输出,它把答案放在第一位,然后在我调用它的地方放一个奇怪的输出.我该怎么办?
- 从 int 中剥离位时,编译器会警告一个转换,但不警告其他转换.有解决方法吗?
- 一个32位版本的应用程序,建立在CentOS 6 x64上,当在较新的Linux上启动时,在"dl_itera
- 给定一个整数 N>0,区间 [0, 2^N) 中有多少个整数正好有 N-1 个设置位?编写一个返回正确答案的简短函数
- 使用 SIMD 基于另一个矢量位值计算值的乘积
- 使用按位运算确定值是否包含另一个值
- 将一个数字拆分为多个数字,每个数字只有一个有效位
- 给定一个无符号整数,很容易交换每个偶数位和奇数位。这可以推广到交换每个偶数和奇数n位吗?
- 将32位和64位应用程序(具有相同代码)编译为一个EXE
- 需要在C 中生成一个从00001到99999的唯一5位数字
- 为什么字符指针会得到一个 32 位值 (ffffff88)
- 将 32 位浮点数和不强制转换的 32 位整数与双精度进行比较,当其中一个值可能太大而无法完全适合另一种类型时
- 在 c++ 中不能将超过 10 位数字放入一个变量中
- 两个 4 位位字段加起来不等于一个字节的大小 - 如何修复?
- 有没有办法构造一个 constexpr 函数来获取双精度的位表示
- GCC 编译器一个字节中有多少位
- 尝试从另一个过程读取32位整数时,ReadProcessMemory崩溃了
- 在64位系统上创建一个非常大的数组的缺点是什么