【问题标题】:C: Address on register or memory?C: 寄存器地址还是内存地址?
【发布时间】:2023-10-16 18:53:01
【问题描述】:

我对 C 中的“寄存器”这个关键字有点困惑。 它似乎告诉编译器它应该存储在寄存器中,这意味着我的一些变量存储在寄存器中,而另一些则存储在内存中?如果是这样,有没有办法找出我的值是存储在寄存器中还是内存中?

例如:

int *x =  (int*) 0x1234;

X 现在似乎没有指向寄存器,因为这样的地址是用于内存的。 但我已经尝试过几次寻找不同的地址(也使用“注册”关键字)。即使在互联网上似乎也没有人关心。

所以我的主要问题是:指针中的地址在指向寄存器时看起来如何?

编辑:在另一个问题的问题结束时,我找不到主要问题的答案。我的问题不是关于“注册”这个关键词,我只是提到了它。

【问题讨论】:

  • 我认为现在的编译器不关注register 关键字。如果我弄错了,请有人纠正我。
  • 对于int *x = (int*) 0x1234;,地址0x1234肯定是一个内存地址,而不是寄存器,但变量x本身有一个位置(其中包含0x1234 ),这可能是一个寄存器或内存中。
  • 编译器最终决定在哪里以及如何存储变量和中间值,但使用的策略取决于您的优化级别。出于性能原因,高度优化的代码大量使用寄存器。如果您想确切了解代码是如何编译的,请查看汇编器输出。
  • 我觉得你有点困惑。首先,register 关键字不是命令,它是对编译器的建议:嘿,如果你同意的话,我希望你为此使用一个寄存器来提高速度。如果编译器认为它有更好的方法来优化代码,则不需要接受该建议。其次,您没有理由真正关心它是否成功放入寄存器中,因为结果您的代码中没有任何变化。第三,寄存器在CPU本身;为什么你认为你可以(或应该)阅读它的地址?
  • 您不能指向 CPU 寄存器,因为它们不是内存映射的。另一方面,如果您指的是外围寄存器;这些 内存映射并在地址空间中具有地址,并且声明将与您拥有的一样 - 除非您确实需要 volatile 类型修饰符。 register 关键字与外设寄存器 无关。解决您的问题 - 它非常不清楚,我会投票重新打开它,或者更有可能指向更好的副本。

标签: c pointers processor


【解决方案1】:

存储类说明符 register 是在 C 语言的第一个版本中创建的,用于指示编译器使用其中一个处理器寄存器来保存具有以下范围的变量允许更快的访问。那时代码编写需要程序员非常关注,因为编译器并不那么聪明,无法检测错误或纠正错误,从而导致代码错误。

无论如何,C 标准在成长过程中出现了一些技巧,使某些细节难以理解。

存储类说明符register 就是其中之一。因此,让我们在评论它们之前开始查看规格。

来自 ISO/IEC 9899:2011,C11 标准:

§6.7.1 存储类说明符

对象标识符的声明 存储类说明符寄存器建议对对象的访问是 尽可能快地。此类建议的程度 有效是实现定义的。 (见注释 119)

还有注释 119:

注意

实现可以将任何寄存器声明简单地视为自动声明。但是,无论是否可寻址 storage 实际使用时,对象任意部分的地址 无法计算使用存储类说明符寄存器声明的, 或者显式地(通过使用一元 & 运算符,如 6.5.3.2)或隐式(通过将数组名称转换为指针,如 6.3.2.1 中所述)。因此,唯一可以应用于 使用存储类说明符 register 声明的数组是 sizeof。

这意味着存储说明符register 是对编译器的建议,以使用处理器寄存器或任何其他方式尽可能快地访问变量,但编译器可以采用不同的决定。它可以将声明的变量视为标准的auto变量。

在最后一种情况下,获取存储地址在技术上是可行的,但正如注释中解释的那样,标准明确禁止这样做,但在 § 的 约束 中也强制执行6.5.3.2 地址和间接运算符

一元& 运算符的操作数应为函数 指示符,[] 或一元 * 运算符的结果,或左值 指定一个不是位域的对象,并且 没有声明为 register 存储类说明符

当然,一些不兼容的编译器可能会让您应用运算符。

现在回到你的问题:

  1. int *x = (int*) 0x1234; X 现在似乎没有指向寄存器, 因为这样的地址是用于内存的。但是我试过几次 查找不同的地址(也使用“注册”关键字)。 即使在互联网上似乎也没有人关心。

您的示例是错误的,您声明了一个指向 int 的指针并为其分配了一个任意地址。这与register 存储说明符无关。

声明register int *x = (int*) 0x1234;,然后尝试将& 运算符应用于x,就像在int **pp = &x; 中一样(注意,我们声明了一个指向int 的指针的指针,这就是我们获取的地址指向int的指针)。

在兼容的编译器上会出现错误。

  1. 所以我的主要问题是:指针中的地址在 寄存器上的点数?

答案很简单:它不像任何东西,因为它不能存在于标准 C 中

【讨论】:

    【解决方案2】:

    当指针指向寄存器时,指针中的地址看起来如何?

    指针是内存的概念,寄存器没有地址。每个处理器都有有限的、固定数量的寄存器(可能是 8 到 16 个)。

    正如其他人所提到的,register 不再真正有用,甚至有时会被编译器忽略。

    要了解寄存器的真正含义,请考虑以下示例:

    int a = k / 53;  // k is an int defined somewhere else...
    int b = a * 9;
    // a is not used after the above line
    

    现在,我们还没有将 a 声明为 register,但任何合理的编译器仍会将 a 保存在寄存器中。

    这样做的原因是为了执行任何操作,操作数必须在某些寄存器中。操作的结果将例如存放在第一个操作数的寄存器中。

    为了从上面的示例中计算a,编译器将编写代码以将k(可能在内存中)加载到某个寄存器(我们称之为寄存器A)和53到另一个。计算完成后,寄存器 A 将包含运算结果。由于无论如何我们要在下一行中将该结果乘以 9,我们可以保持原样,将 9 加载到另一个寄存器并相乘。将值存储到内存中然后将其加载回寄存器只会浪费大量时间。

    请注意,用volatile 声明a 会阻止这样的优化并强制编译器实际存储和加载a。 (尽管volatile 在这里的这个例子根本没有任何意义,而且如果你不连接特殊类型的硬件,也几乎没有用处。)

    【讨论】:

      【解决方案3】:

      在大多数架构中,您不能指向 CPU 寄存器,因为它们不是内存映射的。 (8051 是我能想到的一种架构,核心寄存器 内存映射的)。

      另一方面,如果您指的是外设寄存器;这些 内存映射的,并且在地址空间中有地址,并且声明将与您拥有的一样 - 除非您确实需要 volatile 类型修饰符。

      volatile int* x =  (volatile int*)0x1234 ;
      

      register 关键字与内存映射的外设寄存器无关。

      【讨论】:

        【解决方案4】:

        在现代编译器中,您在 C 中声明的对象不一定只有一个位置。为了优化您的程序,编译器可能有时将其保存在寄存器中,有时保存在堆栈中,有时保存在特定的内存位置。通常,您不需要知道哪个。

        当您获取对象的地址并使用指针时,编译器会使您的程序像对象确实具有固定地址一样工作。通常这是通过将对象放在分配的内存位置来完成的,至少在您使用其地址的持续时间内,但允许编译器通过其他方式实现程序的最终结果。

        如果你用register 声明一个对象,按照 C 标准,你不应该获取它的地址。但是,一些编译器可能允许这样做。

        【讨论】:

        • 请注意问题中关于不是register 关键字的附加部分,以及我对外设寄存器 的评论。这个问题还不清楚,但似乎这可能不是@user84037 认为他在问的问题的答案。