指针是否可能指向CPU寄存器?

7
我想知道指针是否可以指向CPU寄存器,如果不行,尽可能使用引用而不是指针,因为引用对象可能驻留在某个寄存器中,但由指针指向的对象可能不会。

7
取决于底层机器架构,但通常情况下,对于大多数现代计算机,特别是x86架构,答案是“否”。 - Pointy
1
如果你明确要求一个变量在寄存器中,那么你就不能取它的地址。 - Martin York
1
嗯,有点奇怪,没有人提到volatile关键字。它提供的少数保证之一,应该是相关主题。也许我在这里漏掉了什么。 - Hans Passant
1
指针是C/C++的概念,在硬件中并不存在。而寄存器只存在于硬件中,C/C++对寄存器一无所知。因此,不,指针永远不能指向CPU寄存器,因为它们存在于完全不同的世界中 :) - jalf
@dwelch:这实际上是一个定义问题。如果你严格遵循C标准,那么“指针”只是一个抽象的概念——C标准并不规定指针应该如何实现,你当然可以为x86编写一个不使用间接寻址(而是使用查找表或其他方式)的C编译器。另一方面,当然,C语言的设计旨在易于在真实硬件上实现,因此当设计指针特性时,C语言的设计者当然考虑了间接寻址等问题。所以你们两个都是对的,只是从不同的角度看问题 :-)。 - sleske
显示剩余4条评论
7个回答

9
一般来说,CPU寄存器没有内存地址,虽然CPU架构可以使它们可寻址(如果有人知道,请留言),但在C语言中没有标准方法获取寄存器的地址。实际上,如果使用“register”存储类标记变量,则不允许使用“&”运算符获取该变量的地址。
关键问题是别名 - 如果编译器可以确定对象没有别名,则通常可以执行优化(无论对象是通过指针还是引用访问)。总的来说,我认为使用引用而不是指针不会获得任何优化效益。
但是,如果将对象复制到局部变量中,则编译器可以更容易地确定局部变量没有别名,假设您不传递临时变量的地址。这是您可以帮助编译器进行优化的情况;但是,如果复制操作很昂贵,最终可能不划算。
对于适合CPU寄存器的内容,将其复制到临时变量中通常是一个好方法 - 编译器非常擅长将其优化为寄存器。

C或C++标准是否明确规定声明为register的对象不能被取地址? - Armen Tsirunyan
4
@Armen:是的。@MichaelBurr: 微芯片PIC将实际CPU寄存器(例如状态、程序计数器、工作寄存器、索引寄存器)映射到每个内存库的低12字节。 参见http://ww1.microchip.com/downloads/en/DeviceDoc/41413A.pdf第23页。 - Nick T
1
@Armen - 6.5.3.2 地址和间接运算符:“一元&运算符的操作数应该是...(省略了一堆内容)...或者是指定了一个不是位域且没有使用寄存器存储类别说明符声明的对象的左值。” - Michael Burr
你可以将微型芯片 PIC(PIC12,PIC16)的寄存器看作是一个地址。内存通常统称为 F 寄存器。代码可能会根据特殊寄存器的地址引用它们,例如 pc 本身也是可寻址的。像 zpu 这样的基于堆栈的处理器 http://opensource.zylin.com/zpu.htm 中没有寄存器。我还没有仔细研究过 zpu,只是偶然看到它并且很快就发现它是基于堆栈的。我想说的是 8051,但找不到相关信息,我记得有另外一种处理器,但我记不清了。 - old_timer
@ Nick T,你的链接 www1.microchip.com/downloads/en/DeviceDoc/41413A.pdf 显示文件未找到。请更新它。 - uss
显示剩余5条评论

3
当一个引用被传递给函数时,编译器可能会将其实现为一个隐藏的指针,因此更改类型不会有影响。
当创建并在本地使用引用时,编译器可能足够聪明以知道它所指的内容,并将其视为对所引用变量的别名。如果该变量被优化为寄存器,则编译器将知道该引用也是同一寄存器。
指针始终需要指向内存位置。即使在奇怪的体系结构中,其寄存器具有内存位置,编译器似乎也不支持这样的操作。
编辑:以下是启用优化后使用Microsoft C++生成的代码示例。指针和传递的引用的代码是相同的。由于某种原因,按值传递的参数没有最终进入寄存器,即使我重新排列了参数列表。即便如此,一旦将值复制到寄存器中,局部变量和局部引用都使用相同的寄存器而无需重新加载。
void __fastcall test(int i, int * ptr, int & ref)
{
_i$ = 8                         ; size = 4
_ref$ = 12                      ; size = 4
?test@@YIXHPAHAAH@Z PROC                ; test, COMDAT
; _ptr$ = ecx

; 8    :    global_int1 += *ptr;

    mov edx, DWORD PTR [ecx]

; 9    : 
; 10   :    global_int2 += ref;

    mov ecx, DWORD PTR _ref$[esp-4]
    mov eax, DWORD PTR _i$[esp-4]
    add DWORD PTR ?global_int1@@3HA, edx    ; global_int1
    mov edx, DWORD PTR [ecx]
    add DWORD PTR ?global_int2@@3HA, edx    ; global_int2

; 11   : 
; 12   :    int & ref2 = i;
; 13   :    global_int3 += ref2;

    add DWORD PTR ?global_int3@@3HA, eax    ; global_int3

; 14   : 
; 15   :    global_int4 += i;

    add DWORD PTR ?global_int4@@3HA, eax    ; global_int4

2

我认为您想要表达的是引用所指向的整数值是否存储在寄存器中。

通常,大多数编译器将引用视为指针的一种。也就是说,引用只是具有特殊“解引用”语义的指针。因此,与适合存储在寄存器中的整数值不同,引用通常没有优化。引用和指针之间唯一的区别在于引用必须(但未被编译器强制执行)引用有效对象,而指针可以为NULL。


1
在许多(如果不是大多数或全部)实现中,引用通过指针深度实现。因此,我认为通过指针或引用进行操作对于优化器来说几乎没有影响。

1
我认为通常不会这样做。如上面的评论中提到的,有些处理器可以在内存空间中寻址寄存器,但这可能是一个坏主意(除非芯片是为你设计的方式编程)。
实际上,与您所要求的相反情况更多地发生了。优化器可以看到您使用指针及其指向的内容,并根据体系结构可能实际上不使用寄存器来存储指针和指向的内容,而是例如将地址硬编码到指令中而根本不使用寄存器。它可能会将指向的值加载到寄存器中,但使用寄存器来存储地址或使用时间比获取值长。有时效率并不高,它可能会将值保存在寄存器中以便将其地址读回寄存器中,当更改代码可以避免这两个步骤时。这严重取决于程序/代码、指令集和编译器。
因此,与其尝试寻址寄存器以获得一些优化,不如了解编译器和目标,并知道何时最好使用指针、数组或值等。某些结构在大多数处理器上都很有效,而有些结构只在某些处理器上有效,而在其他处理器上则效果不佳。

0

指针指向内存位置。因此,使用指针无法访问CPU寄存器。引用是指针的较弱版本(无法对引用执行算术运算)。但是编译器通常会将变量放入寄存器中以执行操作。例如,编译器可以将循环计数器放入CPU寄存器中以便快速访问。或者将不占用太多空间的函数参数放入寄存器中。在C语言中有一个关键字可以用来请求编译器将某个变量放入CPU寄存器中。该关键字是register

for (int i = 0; i < I; i++)
    for (int j = 0; j < J; j++)
        for (register int k = 0; k < K; k++)
        {
            // to do
        }

有些架构确实具有内存映射寄存器,因此您的答案并不完全正确。 - Paul R
1
register 关键字本质上是对编译器的一个空洞请求,即它可以完全而且默默地被忽略。 - Nick T
@Paul R: 我同意。但我认为这是C语言中最接近的解决方案。 - Donotalo
@Nick T:是的,我在帖子中已经说明了:“……请求编译器……” - Donotalo

0

Michael Burr 是正确的。CPU 寄存器没有内存地址。


1
除非它们被映射到内存中,如Microchip PICs中。 - Nick T
如果我没记错的话,Z8还有一些内存映射寄存器。 - Paul R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接