繁体   English   中英

在 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 示例.c -o 示例 -Wall -Wextra

输出:

警告:隐式常量转换中溢出 [-Woverflow] 寄存器 int x = 4294967296;

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

register int仍然是int ,受制于INT_MAXINT_MIN所有限制,并且有符号溢出是未定义的行为。 它不会将您的 C 源代码变成可移植的汇编语言。

使用register只是告诉编译器阻止您获取变量的地址,因此即使在调试模式下(使用最少的优化),一个非常幼稚的编译器也可以将变量保存在寄存器(的低半部分)中,而不会在稍后遇到任何意外功能。 (当然,现代编译器通常不需要这种帮助,但对于某些register实际上在调试模式下确实有影响。)


如果寄存器有 64 位,为什么 int 的约定应该是 32 位

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

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

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

但是 32 位对于大多数程序来说“足够大”,并且 64 位机器总是具有快速的 32 位整数,因此当从 32 位机器移动到 64 位机器时int保持 32 位。

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

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

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

在 x86-64 上,默认操作数大小仍然是 32 位; mov rax, rcxmov eax, ecx相比需要一个额外的前缀字节 (REX.W),因此 32 位的效率稍高一些。 此外,64 位乘法在某些 CPU 上稍慢,即使在当前的 Intel CPU 上,64 位除法也明显比 32 位慢。 在 x86-64 中使用 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 位时,您希望某些类型成为 32 位整数类型。 (并且long不能比int窄)。 char / short / int / long (或long long )涵盖了所有 4 种可能的操作数大小。 int32_t不能保证在所有系统上都可用,因此如果他们想要 32 位有符号整数,期望每个人都使用它对于可移植代码来说不是一个可行的选择。


脚注1

无论哪种方式,您都可以争论long使用 32 位类型还是 64 位类型更好。 Microsoft 选择将其保持为 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也无关紧要,因为它几乎肯定会被忽略。 无论显式指令如何,编译器都会在有能力和有利的情况下使用寄存器存储,同样也可能不会。

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

您要求编译器将一个值 ( 4294967296 = 0x100000000 ) 放入寄存器中,该值不能以 32 位的形式出现。

register int x = 4294967296;

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM