我知道寄存器变量的概念及其用例,但根据我的尝试,我脑子里几乎没有问题。
我无法访问C中的寄存器变量的地址,尽管我可以使用C ++!为什么?访问寄存器变量的寻址有什么问题吗?
假设我在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'作为寄存器并且不尝试使用'&amp; i'打印地址,那么C ++接受推荐并将'i'存储在寄存器中,这可以是从for循环的运行时间开始,如果'i'在寄存器中,则总是大约4-12 ms。但是,如果我尝试打印变量'i'的地址,那么虽然我没有得到任何错误,但是C ++拒绝推荐,这可以从执行循环的时间开始,如果我没有注册,它总是超过25! !
所以,基本上我不能用C和C ++中的存储类作为寄存器来获取变量的地址!为什么?
答案 0 :(得分:17)
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
),但语言规则无论如何都适用。
答案 1 :(得分:7)
首先,我们来看看相关标准。 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中讨论的一元和&amp;运算符)或隐式计算用存储类说明符寄存器声明的对象的任何部分的地址。 (通过将数组名称转换为指针,如6.3.2.1中所述)。因此,可以应用于使用存储类说明符寄存器声明的数组的唯一运算符是sizeof和_Alignof。
所以,让我们带走我们在这里学到的东西。
关于你在实践中看到的内容:
register int
的地址时,我们可以看到为什么您在C中遇到语法错误,所以让我们超越它。register
。在这种情况下,最好显示您的测试,因为测试本身可能存在问题。如果完整测试没问题,那么你的编译器当然可能正在使用提示来生成更好的代码。for (int i=0; i<100; ++i){}
和for (register int i=0; i<100; ++i) {}
返回相同的代码(即无代码)。答案 2 :(得分:3)
获取变量的地址将强制编译器将其存储在存储器中(除非它是一个寄存器具有地址的架构 - 我认为TI 9900系列处理器是以这种方式实现的,但它是一个模糊的内存来自于1984年)。如果您已告诉编译器使用register
,则会使其不兼容。虽然C ++标准似乎暗示编译器没有义务告诉你,并且实际上可以忽略register
关键字。
C ++ 11 draft n3337,第7.1.1节,第3章
寄存器说明符是对如此声明的变量将被大量使用的实现的提示。 [注意:提示可以忽略,在大多数实现中,如果变量的地址将被忽略 被采取。不推荐使用此功能(参见D.2)。 - 结束说明]
(编辑:是的,TMS 9900确实有“寄存器在内存中”,所以从理论上讲,你可以在该架构中拥有寄存器的地址 - 但架构远不止“寄存器存储在内存中(具有地址)“比”寄存器有地址“)。
答案 3 :(得分:1)
基本答案是,在大多数体系结构中,通用寄存器没有内存地址。通常,指针只是包含对象的存储器位置的(虚拟)存储器地址。这样做效率很高。
可以将指针的概念扩展为指向内存或寄存器。但是,这样做会降低程序速度,因为取消引用指针的代码需要检查指向哪种类型的位置。
答案 4 :(得分:1)
C的许多方面源于允许单通道编译。许多早期编译器会读取一些源代码,生成一些汇编或机器代码,忘记他们刚读取的大部分内容,读取更多代码,生成更多汇编/机器代码等。如果编译器生成机器代码,它可能会需要为前向跳转等内容构建一个后补丁列表,但编译器可以为比其可用内存更大的函数生成代码。
许多机器都有一些可以专用于存储值的寄存器,但编译器无法知道代码中任何给定点的寄存器中哪些变量最有用,除非它知道变量是如何进行的稍后在代码中使用。给出类似的东西:
void test(void)
{
int i,j,*p;
p=&i;
i=j=0;
do
{
j++;
*p+=10;
j++;
...
单通道编译器无法知道它是否可以安全地将j
保留在*p
访问的寄存器中。在j
之前将*p+=10;
刷新到内存并在之后重新加载会否定为其分配寄存器的大部分优点,但编译器跳过刷新并重新加载但上面的代码后跟{{1它会有问题。在执行p=&j;
时,第一个需要在内存中保留j
之后的所有循环传递,但编译器已经忘记了第二次传递所需的代码。
通过指定如果编译器声明为*p+=10;
来解决此问题,编译器可以安全地生成代码,该代码假定没有基于指针的访问会影响它。禁止取得该地址的禁令是恕我直言不必要的过度(*),但描述比允许在更多情况下使用限定词更简单。
(*)即使在今天,语义仍然有用,如果register
承诺编译器可以安全地将变量保存在寄存器中,如果它在获取地址时将其刷新到内存中,并在重新加载时保持不变它直到下一次代码使用变量,向后分支[通过循环结构或转到],或进入使用变量的循环]。
答案 5 :(得分:0)
正是因为它是一个寄存器。地址是存储位置的地址。如果某个东西存在于寄存器中,则根据定义不在主存储器中。
答案 6 :(得分:0)
在C中,我们不能使用寄存器存储来获取变量的地址。我们需要存储带有正常变量的名称。
答案 7 :(得分:0)
您不能使用寄存器变量的地址,因为它没有存储在RAM中。