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

我知道寄存器变量的概念和它的用例,但根据我的尝试,我脑子里几乎没有问题。

  1. 我无法访问C中的寄存器变量的地址,虽然我可以做C ++! 为什么? 访问寄存器变量的寻址有什么问题吗?

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

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

#include #include 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’存储在寄存器中,这可以从运行时间开始对于循环,如果’i’在寄存器中,则循环总是大约4-12 ms。 但是如果我尝试打印变量’i’的地址,那么虽然我没有得到任何错误,但是C ++拒绝推荐,这可以从执行循环的时间开始,如果我没有注册,那么总是超过25! !

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

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

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

    具有存储类说明符register的对象的标识符声明表明对对象的访问尽可能快。 这些建议有效的程度是实施定义的。

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

  • 在C ++中, register是一个不推荐的,无意义的关键字,没有任何效果(除了可能作为编译器提示),声明为register变量仍然只有自动存储。 特别是,C ++ 没有 “寄存器”存储类。 (它只有inheritance自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)。 在块作用域中声明没有存储类说明符或声明为函数参数的变量默认具有自动存储持续时间。

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

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

具有存储类说明符寄存器的对象的标识符声明表明对对象的访问尽可能快。 这些建议有效的程度是实施定义的。)

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

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

  • 在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 draft n3337,第7.1.1节,第3章

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

(编辑:是的,TMS 9900确实有“寄存器在内存中”,所以从理论上讲,你可以在该架构中拥有寄存器的地址 – 但架构远不止“寄存器存储在内存中(具有地址) “比”寄存器有地址“)。

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

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

C的许多方面源于允许单通道编译的愿望。 许多早期编译器会读取一些源代码,生成一些汇编或机器代码,忘记他们刚读取的大部分内容,读取更多代码,生成更多汇编/机器代码等。如果编译器生成机器代码,它可能会需要为前向跳转等内容构建一个后补丁列表,但是编译器可以为比其可用内存更大的函数生成代码。

许多机器都有一些可以专用于存储值的寄存器,但是编译器无法知道代码中任何给定点的寄存器中哪些变量最有用,除非它知道以后如何使用变量在代码中。 给出类似的东西:

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

单通道编译器无法知道它是否可以通过访问*p安全地将j保存在寄存器中。 在*p+=10;之前将j到存储器*p+=10; 然后重新加载它会否定为其分配寄存器的大部分优点,但编译器跳过刷新并重新加载,但上面的代码后跟p=&j; 它会有问题。 在执行*p+=10;第一个需要将j保留在内存中的所有循环传递*p+=10; ,但编译器已经忘记了第二遍所需的代码。

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

(*)即使在今天,如果register承诺编译器可以安全地将变量保存在寄存器中,如果它在获取地址时将其刷新到存储器中,并且在重新加载它时将其保留到下一次使用的代码,那么语义将是有用的。变量,向后分支[通过循环结构或转到],或者进入使用变量的循环]。

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

在C中,我们不能使用寄存器存储来获取变量的地址。 我们需要存储带有正常变量的名称。