在 x86_64 模式下,64 位数字不适合寄存器 int

a 64-bit number does not fit in the register int in x86_64 mode

我的电脑上有一个英特尔处理器,运行 64 位模式,x86_64,其中寄存器的大小为 64 位,如果我使用寄存器这个词,或者使用一些标志优化变量倾向于放在寄存器中,但如果我把一个值放在 32 位以上,编译器会抱怨,即我的 int 不是 64 位,为什么会这样?不是64位的,如果变量放在寄存器里,我连它的内存地址都没有得到?也就是连入栈都没有。

#include <stdio.h>

int main(void) {

    register int x = 4294967296;

    return 0;
}

编译:

gcc example.c -o example -Wall -Wextra

输出:

warning: overflow in implicit constant conversion [-Woverflow] register int x = 4294967296;

在您的平台上 int 可能是 32 位。

您要求编译器将一个不能容纳在 32 位中的值 (4294967296 = 0x100000000) 放入寄存器。

register int x = 4294967296;

但是无论如何,即使您删除了 register 关键字,编译器仍然会出于同样的原因报错。

C register 关键字不会覆盖您选择的 C 类型的宽度。 int 是所有 32 位和 64 位 x86 ABI 中的 32 位类型,包括 x86-64 System V 和 Windows x64。 (long 在 Windows x64 上也是 32 位,但在 Linux / Mac / 其他 x86-641[=123 上是 64 位=].)

A register int 仍然是 int,受 INT_MAXINT_MIN 的所有限制,并且已签名溢出是未定义的行为。 它不会将您的 C 源代码转换为可移植的汇编语言。

使用 register 只是告诉编译器阻止你获取变量的地址,所以即使在调试模式下(最小优化),一个非常天真的编译器可以将变量保持在(低半部分)a在没有 运行 的情况下在函数后面的任何意外中注册。 (当然,现代编译器通常不需要此帮助,但对于某些 register 实际上在调试模式下确实有影响。)


If a register has 64 bits, why should the convention of int be 32 bits

int 是 32 位的,因为没有人希望 int 的数组在 64 位机器上变成 64 位的,缓存占用空间是原来的两倍。

极少数 C 实现具有 int = int64_t(例如在某些 Cray 机器上,我想我已经读过),但即使在 x86-64 之外也非常罕见,其中 32 -bit 是机器代码的“自然”且最有效的操作数大小。即使是 DEC Alpha(激进的 64 位并且从头开始为 64 位设计)我认为仍然使用 32 位 int。

在从 16 位机器发展到 32 位机器时制作 int 32 位是有意义的,因为 16 位有时“太小”了。 (但请记住,ISO C 仅保证 int 至少为 16 位。如果您需要更多,在真正可移植的程序中,您最好使用 longint_least32_t。)

但是 32 位对于大多数程序来说“足够大”,而 64 位机器总是有快速的 32 位整数,所以 int 在从 32 位移动到 32 位时保持 32 位64位机器。

在某些机器上,16 位整数的支持不是很好。例如在 MIPS 上使用 uint16_t 实现 16 位换行需要额外的 AND 立即指令。因此,将 int 设为 16 位类型将是一个糟糕的选择。

在 x86 上,您可以只使用 16 位操作数大小,并在复制时使用 movzx 而不是 mov,但是 int 是 32 位的“正常”在 32 位机器上,所以 x86 32 位 ABI 都选择了那个。

当 ISA 从 32 位扩展到 64 位时,使 int 更宽 的性能理由为零,这与 16->32 的情况不同。 (同样在那种情况下,short 保持 16 位,因此 16 位和 32 位整数都有一个类型名,甚至在 C99 stdint.h 存在之前)。

在 x86-64 上,默认操作数大小仍然是 32 位; mov rax, rcxmov eax, ecx 相比需要一个额外的前缀字节 (REX.W),因此 32 位的效率稍微高一些。此外,64 位乘法在某些 CPU 上稍慢,而 64 位除法即使在当前的 Intel CPU 上也比 32 位慢得多。


此外,如果编译器想要提供可选的 int32_t,则它们需要 int32_t 的原始类型。 (固定宽度的 2 的补码类型是可选的,不像 int_least32_t 等不能保证是 2 的补码或没有填充。)

具有 16 位 short 和 64 位 int 的编译器可以 具有特定于实现的类型名称,例如 __int32用作 int32_t / uint32_t 的 typedef,所以这个论点并不是一个完全的障碍。但这会很奇怪。

当从 16 位增长到 32 位时,将 int 更改为比 ISO C 最小值更宽是有意义的,因为您仍然有 short 作为 16 位的名称。 (这个论点不是很好,因为在 32 位系统上确实有 long 作为 32 位整数的名称。)

但是当增长到 64 位时,您希望 some 类型成为 32 位整数类型。 (而且 long 不能比 int 窄)。 char / short / int / long(或long long)涵盖所有 4 种可能的操作数大小。 int32_t 不是'保证在所有系统上都可用,因此期望每个人在需要 32 位带符号整数时都使用它不是可移植代码的可行选择。


脚注 1:

您可以争论 long 是 32 位还是 64 位类型更好。微软选择保持 32 位意味着使用 long 的结构布局可能不会在 32 位和 64 位代码之间改变(但如果它们包含指针,它们会改变)。

ISO C 要求 long 至少是 32 位类型(实际上他们根据可以表示的最小值和最大值来定义它,但在其他地方他们确实要求整数类型是二进制的带有可选填充的整数)。

无论如何,有些代码使用long是因为它需要32位类型,但不需要64位;在许多情况下,更多的比特并不更好,只是不需要它们。

在像 x86-64 System V 这样的单个 ABI 中,始终将 long 与指针的宽度保持相同是半方便的,但由于可移植代码始终需要使用 unsigned long longuint64_tuint_least64_tuintptr_t 取决于用例,x86-64 System V 选择 64 位长可能是错误的。

OTOH,本地人的更宽类型有时可以通过在索引指针时避免符号扩展来保存指令,但带符号溢出是未定义行为的事实通常会让编译器在方便时在 asm 中扩展 int

register关键字在这里无关紧要; int 数据类型在相关平台上仍然是 32 位。

#include <stdio.h>
#include <stdint.h>

int main(void) 
{
    int64_t x = 4294967296;
    return 0;
}

register 也是无关紧要的,因为它几乎肯定会被忽略。无论显式指令如何,编译器都会在可行且有利的情况下使用寄存器存储,同样也可能不会。