C和C++中寄存器变量的地址

address of register variable in C and C++

本文关键字:变量 地址 寄存器 C++      更新时间:2023-10-16

我知道寄存器变量的概念及其用例,但根据我的尝试,我脑海中几乎没有什么问题。

  1. 我不能访问C中寄存器变量的地址,尽管我可以用C++访问!为什么?访问寄存器变量的寻址是否存在问题?

  2. 假设我将C++中的一个字符串变量声明为寄存器,那么该变量将存储在哪里?将C++中的"string"等非数字数据类型的存储类声明为寄存器有什么意义??

更新:我认为C++允许我们获取寄存器变量的地址,因为我的程序中没有出现任何错误,如下所示:

#include<iostream>
#include<time.h>
using namespace std;
clock_t beg, en;
int main(){
    int j, k=0;
    beg=clock();
    for(register int i=0;i<10000000;i++){
        /*if(k==0){
            cout<<&i<<endl;    // if this code is uncommented, then C++ rejects the recommendation to make 'i' as register
            k++;
        }*/
    }
    en=clock();
    cout<<en-beg<<endl;
    cout<<&j<<endl<<&k;
    return 0;
}

我观察到的是,如果我将变量"I"作为寄存器,并且不尝试使用"&i',则C++接受推荐并将'i'存储在寄存器中,这可以从for循环的运行时间推断出来,如果'i'在寄存器中的话,循环的运行速度将始终在4-12ms左右。但是,如果我试图打印变量"I"的地址,尽管我没有得到任何错误,但C++拒绝了建议,这可以从执行循环的时间推断出来,如果我没有注册,循环的时间总是超过25!!

所以,基本上我不能在C和C++中都用存储类作为寄存器来获取变量的地址!!为什么?

C和C++是不同的语言。

  • 在C中,不能使用register存储来获取变量的地址。参见C11 6.7.1/6:

    具有存储类说明符register的对象的标识符声明建议尽可能快地访问该对象。在多大程度上建议的有效性取决于执行情况。

    脚注:实现可以将任何register声明简单地视为auto声明。[…]

  • 在C++中,register是一个不推荐使用的、没有意义的关键字,它没有任何作用(除了可能用作编译器提示),声明为register的变量仍然只有自动存储。特别是,C++没有"寄存器"存储类。(它只有存储类说明符,继承自C。)参见C++11,7.1.1/3:

    register说明符是对实现的一个提示,表明如此声明的变量将被大量使用。〔注意:提示可以被忽略,在大多数实现中,如果采用变量的地址,它将被忽略。这种用法不推荐使用〔…〕

即使在C中,实际上也不能保证寄存器存储是如何实现的(实现可以自由地将register视为auto),但语言规则无论如何都适用。

首先,让我们看看相关的标准。对于这个关键字,C和C++总是有可能有不同的含义。

C++2011第7.1.1节第2段和第3段:

寄存器说明符只能应用于块(6.3)中声明的变量名称或函数参数(8.4)。它指定命名变量具有自动存储持续时间(3.7.3)。在块范围内声明的变量或声明为函数参数的变量默认具有自动存储时间。

寄存器说明符是对实现的一个提示,表明如此声明的变量将被大量使用。[注意:提示可以被忽略,在大多数实现中,如果采用变量的地址,它将被忽略。这种使用是不推荐的(见D.2)。--结束注释]

C 2011第6.7.1节第6段和脚注121:

使用存储类说明符寄存器声明对象的标识符表明访问该对象的速度应尽可能快。这些建议的有效程度取决于执行情况。)

该实现可以将任何寄存器声明简单地视为自动声明。然而,无论是否实际使用了可寻址存储,用存储类说明符寄存器声明的对象的任何部分的地址都不能显式(通过使用6.5.3.2中讨论的一元运算符)或隐式(通过将数组名称转换为6.3.2.1中讨论的指针)计算。因此,可以应用于用存储类说明符寄存器声明的数组的唯一运算符是sizeof和_Alinof。

所以,让我们把我们在这里学到的东西拿走。

  • 在C++中,register关键字没有任何意义。它确实起到了编译器提示的作用,但建议大多数编译器无论如何都会忽略该提示
  • 在C中,register关键字保留了的含义。例如,在这里,我们不允许获取对象的地址(参见引用的脚注)。实现可能会忽略寄存器提示,并将对象放在内存中,但关键字确实限制了您可以对对象执行的操作。这些限制应该使编译器能够更好地优化对对象的访问,但也有可能(就像C++案例中建议的那样),编译器无论如何都能够推断出这一点

至于你在实践中看到的:

  • 我们可以理解为什么当您尝试获取register int的地址时,会在C中出现语法错误,所以让我们跳过它
  • 您声称在C++中看到了性能差异,这取决于您是否使用register。在这种情况下,最好展示您的测试,因为测试本身可能存在问题。如果完整的测试是好的,那么编译器肯定有可能使用提示来生成更好的代码
  • 不过,您显示的代码确实很奇怪。这是因为优化下的编译器可能只会从代码中删除整个for循环。for循环没有副作用。编译器很可能(也是首选)会返回与for (int i=0; i<100; ++i){}for (register int i=0; i<100; ++i) {}相同的代码(即没有代码)

获取变量的地址将迫使编译器将其存储在内存中(除非是寄存器有地址的架构-我认为TI 9900系列处理器是这样实现的,但它是一个模糊的内存,大约在1984年)。如果您告诉编译器使用register,则会使其不兼容。尽管C++标准似乎表明编译器没有义务告诉您,而且实际上可以忽略register关键字。

C++11草案n3337,第7.1.1节,项目符号3

寄存器说明符是对实现的一个提示,表明如此声明的变量将被大量使用。[注意:提示可以被忽略,在大多数实现中,如果变量的地址被拿走。不赞成使用这种方法(见D.2)。--结束注释]

(编辑:是的,TMS 9900确实有"内存中的寄存器",所以理论上,你可以在该体系结构中拥有寄存器的地址——但该体系结构更像是"寄存器存在于内存中(有地址)",而不是"寄存器有地址")。

基本答案是,在大多数体系结构上,通用寄存器没有内存地址。通常,指针只是包含对象的内存位置的(虚拟)内存地址。这是为了效率。

可以将指针的概念扩展到指向内存或寄存器。然而,这样做会降低程序速度,因为取消引用指针的代码需要检查指针指向的位置类型。

C的许多方面都源于允许单次编译的愿望。许多早期的编译器会读一点源代码,生成一些汇编或机器代码,忘记他们刚刚读的大部分内容,再读一点代码,生成更多的汇编/机器代码,等等,但是编译器可以为大于其可用RAM的函数生成代码。

许多机器都有一些寄存器专门用于存储值,但编译器无法知道在代码的任何给定点,哪些变量可以最有效地保存在寄存器中,除非它知道变量稍后将如何在代码中使用。给定以下内容:

 void test(void)
 {
   int i,j,*p;
   p=&i;
   i=j=0;
   do
   {
     j++;
     *p+=10;
     j++;
     ...

单程编译器将无法知道它是否可以在对CCD_ 17的访问期间安全地将CCD_。在*p+=10;之前将j刷新到内存中,然后再重新加载,这将抵消为其分配寄存器的大部分优势,但编译器跳过了刷新和重新加载,但上面的代码后面跟着p=&j;,这将出现问题。第一次循环之后的所有循环都需要在执行*p+=10;时将j保存在内存中,但编译器可能已经忘记了第二次循环所需的代码

通过指定如果编译器被声明为register,编译器可以安全地生成代码,该代码假定没有基于指针的访问会影响它。禁止获取地址是IMHO不必要的超范围(*),但它比允许在更多情况下使用限定符的描述更简单。

(*)即使在今天,如果register承诺编译器可以安全地将变量保存在寄存器中,如果它在获取地址时将其刷新到内存中,并推迟重新加载,直到下一次代码使用变量、向后分支[通过循环构造或goto]或进入使用变量的循环]。

正是因为它是一个寄存器。地址是内存位置的地址。如果某个东西驻留在寄存器中,则根据定义它不在主存储器中。

在C中,我们不能使用寄存器存储来获取变量的地址。我们需要使用普通变量名称进行存储。

您不能获取寄存器变量的地址,因为它没有存储在RAM中。