32 位兼容模式下的 sizeof(int*)

sizeof(int*) in 32-bit compatibility mode

运行 以下在 Linux x86-64 上用 gcc -m32

编译
#include <stdio.h>
#include <limits.h>

int main() {
    int a = 4;
    int* ptr = &a;

    printf("int* is %d bits in size\n", CHAR_BIT * sizeof(ptr));
    return 0;
}

结果

int* is 32 bits in size

为什么我说服自己它应该是 64 位的(在执行之前):因为它在 64 位计算机上是 运行 为了寻址我们需要 64 位的内存。由于 &a 是存储值 4 的地址,因此它应该是 64 位。编译器可以通过为所有指针设置相同的偏移量来实现一个技巧,因为它在兼容模式下是 运行,但它不能保证多次调用 malloc 后数据一致。这是错误的。为什么?

现代CPUs有一个memory management unit,这使得每个程序都有自己的地址space成为可能。您甚至可以让两个不同的程序使用相同的地址。该单元也是检测分段错误(访问冲突)的单元。这样一来,程序使用的地址与连接CPU和包括RAM在内的外围设备的地址总线上的地址不同,因此OS分配32位地址是没有问题的到一个程序。

在硬件层面上,典型的 x86-64 处理器具有 32 位兼容模式,其行为类似于 x86 处理器。这意味着内存使用 4 个字节寻址,因此您的指针是 32 位。

在软件层面,64位内核允许32位进程在此兼容模式下运行。

这就是 'old' 32 位程序在 64 位机器上 运行 的方式。

编译器,尤其是带有 -m32 标志的编译器,为 x86 寻址编写代码,所以这就是 int* 也是 32 位的原因。

x86-64 机器 运行 64 位 OS 在 "compat" 模式下运行 32 位进程,这与 "legacy" 模式不同。在兼容模式下,user-space(即 32 位程序的观点)与传统模式下的系统(32 位一切)的工作方式相同。

但是,内核仍然是64位的,可以将compat-mode进程的虚拟地址space映射到物理地址space的任意位置。 (因此两个不同的 32b 进程可以各自使用 4GB 的 RAM。)IDK 如果兼容进程的页表需要不同于 64 位进程。我找到了 http://wiki.osdev.org/Setting_Up_Long_Mode,它有一些东西但没有回答那个问题。

在兼容模式下,系统调用将 CPU 切换到 64b 长模式,而系统调用的 returns 切换回来。以 user-space 指针作为参数的内核函数需要简单的包装器来执行任何必要的操作,以从 kernel-space.

获取适当的地址以供使用

高级答案是,兼容模式需要与传统模式(32 位内核)一样快的所有硬件支持。

IIRC,MMU 硬件将 32 位虚拟地址零扩展为 64 位,因此内核只是相应地设置页表。

如果在 64 位代码中使用地址大小覆盖前缀,则由所涉及的 32 位寄存器形成的 32 位地址将被零扩展。 (有一个 x32 ABI 用于不需要超过 4GB RAM 的代码,并且会受益于更小的指针,但仍然希望更多寄存器的性能优势,并且它们是 64b。)