理论上是的,但只有永久固定到该寄存器的全局才真正合理。
(当然,首先假设一个带有内存映射 CPU 寄存器的 ISA1;通常只有微控制器 ISA 是这样的;这使得高性能实现变得更加困难。)
当您将指针传递给qsort 或printf 等函数或您自己的函数时,指针必须保持有效(保持指向同一个对象)。但是复杂的函数通常会将一些寄存器保存到内存(通常是堆栈)to be restored at the end of the function,并且在该函数内部会将它们自己的值放入这些寄存器中。
所以指向 CPU 寄存器的指针将指向其他东西,可能是函数的局部变量之一,当该函数取消引用您传递给它的指针时,如果您只选择一个普通的调用保留寄存器。
我看到解决此问题的唯一方法是为程序范围内的特定 C++ 对象保留一个寄存器。就像在全局范围内类似于 GNU C/C++ register char foo asm("r16"); 的东西,但带有一个假设的编译器,不会阻止你获取它的地址。这样一个假设的编译器必须比 GCC 更严格,以确保对于通过指针进行的每次内存访问,全局的值始终在该寄存器中,这与 GCC documents for register-asm globals 不同。您必须重新编译库以不将该寄存器用于任何内容(例如 gcc -ffixed-r16 或让他们查看定义。)
当然,C++ 实现也可以决定自己为某个 C++ 对象(可能是全局对象)执行所有这些操作,包括生成所有库代码以尊重整个程序的寄存器分配。
如果我们只讨论在有限范围内执行此操作(不适用于调用未知函数),那么编译 int *p = &x; 以获取 CPU 寄存器的地址肯定是安全的x 当前在,如果escape analysis 证明p 的所有使用都是有限的。我想说这将是无用的,因为任何此类证明都会为您提供足够的信息来优化间接并编译 *p 以作为寄存器而不是内存访问,但有一个用例:
如果您有两个或更多变量并且在取消引用p之前执行if (condition) p = &y;,编译器可能知道x在评估*p时肯定仍然在同一个寄存器中,但不知道p 是指向x 还是y。因此,将x 或y 保留在寄存器中可能很有用,特别是如果它们也被其他混合有p derefs 的代码直接读/写。
当然,我一直在假设一个“正常”的 ISA 和一个“正常”的调用约定。可以想象奇怪而奇妙的机器,和/或它们或普通机器上的 C++ 实现,它们的工作方式可能会有很大不同。
ISO C++ 对此有何评论:不多
ISO C++ 抽象机只有有内存,每个对象都有一个地址。 (如果从不使用地址,则遵循 as-if 规则。)将数据加载到寄存器中是一个实现细节。
所以是的,在像 AVR(8 位 RISC 微控制器)或 8051 这样的机器中,一些 CPU 寄存器是内存映射的,C++ 指针可以指向它们1。在 AVR2 等一些微控制器上,拥有内存映射的 CPU 寄存器是一件事情。 (例如What is the benefit of having the registers as a part of memory in AVR microcontrollers? 有一个图表。(并且问了一个奇怪的问题,为什么我们有寄存器,而不是仅仅使用内存地址,如果它们将被内存映射。)
这个AVR Godbolt link 并没有真正显示太多,主要是在玩弄一个 GNU C register-asm global。
脚注 1:在普通 ISA 的普通 C++ 实现中,C++ 指针非常直接地映射到可以从 asm 中以某种方式取消引用的机器地址。 (Perhaps very inconveniently 在 6502 等机器上,但仍然如此)。
在没有虚拟内存的机器中,这样的指针通常是物理地址。 (假设是普通的平面内存模型,而不是分段的。)我不知道任何具有虚拟内存 和 内存映射 CPU 寄存器的 ISA,但是有很多我不知道的晦涩的 ISA .如果存在,将寄存器映射到 virtual 地址空间的固定部分可能是有意义的,因此可以在 TLB 查找的同时检查该地址的寄存器访问。无论哪种方式,它都会使 ISA 的流水线实现成为一个巨大的痛苦,因为检测像 RAW hazards 这样需要绕过转发(或停止)的危害现在涉及检查内存访问。普通 ISA 只需要在解码机器指令时将寄存器编号相互匹配。由于内存允许通过寄存器进行间接寻址,memory disambiguation/存储转发需要与检测指令何时读取先前寄存器写入的结果进行交互,因为读取或写入可能是通过内存进行的。
有旧的非流水线 CPU 具有虚拟内存,但流水线是您永远希望在现代 ISA 上对寄存器进行内存映射的主要原因之一,并希望将其用作主要内存与性能相关的台式机/笔记本电脑/移动设备的 CPU。如今,包含虚拟内存的复杂性但不流水线化设计已经没有什么意义了。有一些流水线微控制器/低端CPU没有虚拟内存。
脚注 2:内存映射 CPU 寄存器在现代主流 32 位和 64 位 ISA 上基本上不存在。 Do general purpose registers are generally memory mapped?
具有内存映射 CPU 寄存器的微控制器通常将寄存器文件实现为内部 SRAM 的一部分,无论如何它们都必须充当常规内存。
在 ARM、x86-64、MIPS 和 RISC-V 以及所有类似的 ISA 中,寻址寄存器的唯一方法是将寄存器编号编码为指令的机器代码。寄存器间接只能通过自修改代码实现,而 C++ 并不需要这些代码,并且正常的实现不使用这些代码。此外,寄存器编号是与内存分开的地址空间。例如ARM 有 16 个基本整数 reg,因此像 add r0, r1, r2 这样的指令将在该机器指令的编码中具有三个 4 位字段,每个操作数一个。 (在 ARM 模式下,不是 Thumb。)这些寄存器编号与内存地址 0、1 或 2 无关。
请注意,memory-mapped I/O 寄存器在所有现代 ISA 上都很常见,通常与 RAM 共享物理地址空间。 I/O 地址是通常称为寄存器,但寄存器在外围设备中,如网卡,而不是在 CPU 中。读取或写入它会产生一些副作用,因此在 C++ 中,您通常会使用 volatile int *constexpr ioport = 0x1234; 或其他用于 MMIO 的东西。 MMIO 寄存器绝对不是您可以在 AArch64 add w0, w1, w2 等指令中使用的通用整数寄存器之一。