【问题标题】:May a pointer ever point to a cpu register?指针可以指向 cpu 寄存器吗?
【发布时间】:2010-10-14 20:15:17
【问题描述】:

我想知道一个指针是否可以指向一个 cpu 寄存器,因为在这种情况下它可能不会,在可能的情况下使用引用而不是指针会给编译器做一些优化的机会,因为被引用的对象可能驻留在某个寄存器中,但是指针指向的对象可能不会。

【问题讨论】:

  • 完全依赖于底层机器架构,但对于大多数现代机器,尤其是 x86,通常答案是“否”。
  • 如果你明确要求一个变量在一个寄存器中,那么你不能获取它的地址。
  • 如您所说,优化将由编译器执行。特定编译器的编写者确实(应该)知道指针是否可以指向它编译到的体系结构上的寄存器。
  • 嗯,有点奇怪,这里没有人提到 volatile 关键字。它提供的少数保证之一,它应该是主题。也许我在这里遗漏了一些东西。
  • 指针是一个 C/C++ 概念,它不存在于硬件中。而且寄存器只存在于硬件中,C/C++对寄存器一无所知。所以不,指针永远不能指向 CPU 寄存器,因为它们存在于完全独立的世界中。 :)

标签: c++ optimization pointers reference


【解决方案1】:

一般来说,CPU 寄存器没有内存地址,尽管 CPU 架构可以使它们可寻址(我不熟悉任何 - 如果有人知道,我会很感激评论)。但是,在 C 语言中没有获取寄存器地址的标准方法。事实上,如果您使用 register 存储类标记变量,则不允许使用 & 运算符获取该变量地址。

关键问题是别名 - 如果编译器可以确定一个对象没有别名,那么它通常可以执行优化(无论是通过指针还是引用访问对象)。我认为使用指针上的引用不会获得任何优化好处(无论如何)。

但是,如果您将对象复制到局部变量中,则编译器可以更轻松地确定本地没有别名(假设您不传递临时地址)。这是您可以帮助编译器优化的情况;但是,如果复制操作成本高昂,最终可能无法获得回报。

对于适合 CPU 寄存器的内容,复制到临时文件通常是一种不错的方法 - 编译器非常擅长将这些内容优化到寄存器中。

【讨论】:

  • C 或 C++ 标准是否明确规定不能将对象声明的寄存器作为其地址?
  • @Armen:是的。 @MichaelBurr:Microchip PIC 将实际的 CPU 寄存器(例如状态、程序计数器、工作寄存器、索引寄存器)映射到每个内存库的低 12 个字节。 ww1.microchip.com/downloads/en/DeviceDoc/41413A.pdf 页。 23
  • @Armen - 6.5.3.2 地址和间接运算符:“一元 & 运算符的操作数应该是......(省略了一堆东西)......或者一个左值,它指定一个对象不是位字段,也没有使用寄存器存储类说明符声明”
  • @Michael:谢谢,我从来不知道。但是现代编译器是否真的将寄存器对象放入寄存器中,因为我听说他们完全忽略了寄存器提示并自己决定在寄存器中放入什么
  • @Armen:我还听说编译器在确定它们如何将对象分配给寄存器时基本上忽略了register 关键字——标准当然允许这种行为。我还听说你不应该使用关键字,因为你会弄乱编译器的更好判断。我不确定哪个极端更真实。事实上,这可能取决于您使用的编译器和选项。
【解决方案2】:

当一个引用被传递给一个函数时,编译器可能会将它实现为一个隐藏指针——所以改变类型并不重要。

当在本地创建和使用引用时,编译器可能足够聪明,可以知道它所引用的内容并将其视为被引用变量的别名。如果变量被优化为一个寄存器,编译器就会知道引用也是同一个寄存器。

指针总是需要指向一个内存位置。即使在为寄存器提供内存位置的奇怪架构上,编译器似乎也不太可能支持这样的操作。

编辑: 例如,这里是从 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

【讨论】:

    【解决方案3】:

    我认为您的意思是引用引用的整数值是否驻留在寄存器中。

    通常,大多数编译器将引用视为指针。也就是说,引用只是具有内置特殊“取消引用”语义的指针。因此,遗憾的是,与可以放入寄存器的整数值不同,通常没有优化。引用和指针之间的唯一区别是引用必须(但不是由编译器强制执行)引用有效对象,而指针可以为 NULL。

    【讨论】:

      【解决方案4】:

      在许多(如果不是大多数或全部)实现中,引用是通过指针在内部实现的。所以我认为通过指针或引用来做这件事对于优化器来说几乎是无关紧要的。

      【讨论】:

        【解决方案5】:

        我会说一般不会。正如上面评论中提到的,有些处理器可以在内存空间中寻址寄存器,但这可能是个坏主意(除非芯片是为您设计的那样编程)。

        这更像是与您所要求的实际情况相反。优化器可以看到你在用指针做什么以及它指向什么,并且取决于架构可能实际上并不使用指针寄存器和寄存器来保存它指向的内容,但例如可能将地址硬编码到指令中根本没有寄存器。可以将指向的值加载到寄存器中,但使用寄存器作为地址,或者使用它的时间超过获取值所需的时间。有时效率不高,它可能会将寄存器中的值保存到内存中,以便它可以使用其地址将其读回寄存器,而更改代码将避免这两个步骤。它在很大程度上取决于程序/代码以及指令集和编译器。

        因此,与其尝试寻址寄存器来尝试进行一些优化,不如了解编译器和目标,并知道何时使用指针、数组或值等更好。有些结构在大多数处理器上运行良好,而有些结构只适用一个很好,但另一个不好。

        【讨论】:

          【解决方案6】:

          指针指向内存位置。因此无法使用指针访问 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
                  }
          

          【讨论】:

          • 有些架构确实有内存映射寄存器,所以你的答案并不完全正确。
          • register 关键字本质上是对编译器的空洞请求,可以完全无声地忽略它。
          • @Paul R:我同意。但我认为这是 C 语言中最接近的解决方案。
          • @Nick T:是的,我在帖子中说:“...请求编译器...”
          【解决方案7】:

          迈克尔·伯尔是正确的。 CPU 寄存器没有内存地址。

          【讨论】:

          • 除非它们被映射到内存,比如在 Microchip PIC 中
          • Z8 也有内存映射寄存器 IIRC 组。
          猜你喜欢
          • 1970-01-01
          • 2021-06-10
          • 2011-02-01
          • 2021-02-14
          • 1970-01-01
          • 2016-08-11
          • 2015-02-16
          • 2016-03-20
          相关资源
          最近更新 更多