如何将C++变量存储在寄存器中

17

关于寄存器变量的存储,我希望能得到一些澄清:如果我们在代码中声明了一个寄存器变量,有没有办法确保它只会被存储在寄存器中?

#include<iostream>

using namespace std;

int main()
{
    register int i = 10;// how can we ensure this will store in register only.
    i++;
    cout << i << endl;
    return 0;
}

12
C和C++无法保证程序运行的硬件甚至具有“寄存器”,这仅仅是对编译器的提示。 - Stephen Canon
6
在C语言中,声明一个变量为"register"不仅仅是暗示编译器将其保存在寄存器中,它还禁止在该变量处取地址。但是,这并不意味着它一定会被保存在寄存器中。 - Daniel Fischer
这篇与程序开发有关的内容需要翻译成中文。请仅返回翻译后的文本:“这个问题是否对您有所帮助:Stack Overflow如何知道寄存器变量存储在哪里?这不是您的回答,但您可能会发现它很有趣。” - Grijesh Chauhan
1
进一步说,关于“你为什么在乎”的评论。我们可以说编译器总是将变量存储在寄存器中——现在呢?我们可以说所有简单的本地变量都始终存储在寄存器中——现在呢?这没有逻辑意义,除非它具有某些可见/有用的副作用,比如“粉色 int i = 2;”请求将变量存储在粉色内存中。 - pm100
8个回答

31
你不能这样做。这只是给编译器的一个提示,表明变量被大量使用。下面是C99的措辞: ``` 对于具有存储类别说明符“register”的对象的标识符的声明建议访问对象尽可能快速。这些建议的有效程度由实现定义。 ```
以下是C++11的措辞: ``` "register"说明符是向实现提示所声明的变量将被频繁使用。 [注:如果变量的地址被取出,提示就会被忽略;在大多数实现中,这个提示也会被忽略。此用法已弃用(参见D.2)。--end note] ```
事实上,“register”存储类别说明符在C++11中已被弃用(附录D.2): ``` 作为“存储类别说明符”(7.1.1)的关键字“register”的使用已过时。 ```
请注意,在C语言中,你不能获取“register”变量的地址,因为寄存器没有地址。在C++中,这个限制被取消了,获取地址几乎可以确保变量不会最终存储在寄存器中。
许多现代编译器在C++中简单地忽略了“register”关键字(除非它以无效的方式使用),因为它们比“register”关键字有用时更擅长优化。我预计,在特定目标平台上,编译器会更认真地处理它。

1
总是让我感到惊讶的是看到每个本地变量都标记为“register”的代码。 - Pete Becker
4
谁需要内存条?我全是寄存器! - Joseph Mansfield
你的论点是仅限于C++。C对寄存器变量施加了限制。 - Jens Gustedt
@JensGustedt 在 C 语言中仍然只是一个提示。我注意到当我特别谈论 C++ 时。我对答案进行了更详细的阐述。 - Joseph Mansfield
1
啊,这让人怀念那些可以编写 register(4) 并且变量最终会在寄存器 4 中的日子。(然后你会跟着几个 gen 过程调用来完成汇编工作。) - Hot Licks
@sftrabbit,不,in在C语言中不仅仅是一个提示。在C语言中,获取“register”变量的地址是一种约束违规。这是一个真正的语义差异。能够编译通过C++的程序可能无法通过C语言的编译。 - Jens Gustedt

6
register 关键字在 C 和 C++ 中有不同的含义。在 C++ 中,它实际上是多余的,甚至似乎已经被废弃了。
但在 C 中,情况有所不同。首先,不要按照关键字的字面意思来理解,它并不总是与现代 CPU 上的 "硬件寄存器" 有关。对 register 变量施加的限制是您不能获取它们的地址,即不允许使用 & 操作符。这使您可以将变量标记为进行优化,并确保编译器会在尝试获取其地址时向您发出警告。特别地,一个同时具有 const 限定符的 register 变量永远不可能与其他变量重名,因此它是进行优化的好选择。
在 C 中系统地使用 register 强制您考虑每个获取变量地址的地方。这可能不是您想在 C++ 中做的事情,因为 C++ 严重依赖于对象的引用等内容。这可能是为什么 C++ 没有从 C 中复制 register 变量的这个属性的原因之一。

我认为你在第二段的第一句话中想表达的是“逐字逐句”(或“照字面意思”),而不是“冗长啰嗦”的意思。 - Jeff Hammond

5

一般情况下是不可能的。具体而言,可以采取一些措施来增加概率:

使用适当的优化级别,例如 -O2

保持变量数量较小

register int a,b,c,d,e,f,g,h,i, ... z;  // can also produce an error
// results in _spilling_ a register to stack
// as the CPU runs out of physical registers

不要获取寄存器变量的地址。
register int a;
int *b = &a;  /* this would be an error in most compilers, but
                 especially in the embedded world the compilers
                 release the restrictions */

在一些编译器中,你可以提出建议。
register int a asm ("eax");  // to put a variable to a specific register

你的论点只适用于C++。C对“register”变量有限制。此外,你在gcc中的示例是误导性的。“asm”对于gcc来说不是“建议”。如果你指定了一个寄存器,那么该寄存器应该存在并且被使用。 - Jens Gustedt
GCC文档已更新:register ... asm() 本地变量不再保证除了为“r”约束选择该寄存器之外的任何内容。实际上,GCC仍然使用指定的寄存器;clang在asm语句之外不使用。相关链接:将寄存器值读入C变量 - Peter Cordes

4
一般来说,CPP编译器(例如g++)会对代码进行许多优化。因此,当您声明一个寄存器变量时,并不一定意味着编译器将直接将该值存储在寄存器中。也就是说,代码“register int x”可能不会导致编译器直接将该int存储在寄存器中。但如果我们能强制编译器这样做,那么我们可能会成功。
例如,如果我们使用以下代码片段,则可以强制编译器执行我们所需的操作。编译以下代码片段可能会出错,这表明int实际上正在直接存储在寄存器中。
int main() {
    volatile register int x asm ("eax"); 
    int y = *(&x);
    return 0;
}

对我来说,在这种情况下,g++编译器会抛出以下错误。

[nsidde@nsidde-lnx cpp]$ g++ register_vars.cpp 
register_vars.cpp: In function ‘int main()’:
register_vars.cpp:3: error: address of explicit register variable ‘x’ requested

代码中的'volatile register int x asm ("eax")'指示编译器将整数x存储在'eax'寄存器中,并在此过程中不进行任何优化。这将确保该值直接存储在寄存器中。这就是为什么访问变量地址会引发错误的原因。

或者,C编译器(gcc)可能会在以下代码中出现错误。

int main() {
    register int a=10;
    int c = *(&a);
    return 0;
}

对于我来说,在这种情况下,gcc编译器会报以下错误。
[nsidde@nsidde-lnx cpp]$ gcc register.c 
register.c: In function ‘main’:
register.c:5: error: address of register variable ‘a’ requested

1
这个 asm("eax") 语法正是我在寻找的。感谢你实际回答了我的问题。 - benathon
他没有链接到包含有用信息的asm的文档。特别是:此功能的唯一支持用途是在调用扩展asm时指定输入和输出操作数的寄存器。 因此,除非/直到您调用扩展asm,否则不能保证任何特定值将在eax中的任何特定时间内。这就回到了其他人所说的:它现在并不真正意味着什么。 - David Wohlferd

3

这只是对编译器的提示;你不能强制它将变量放置在寄存器中。无论如何,编译器作者可能比应用程序员更了解目标架构,因此更适合编写能够进行寄存器分配决策的代码。换句话说,使用register不太可能实现任何目的。


你的论点只适用于C++。C对寄存器变量有限制。 - Jens Gustedt

2
"register"关键字是编译器需要适应只有2MB RAM(共享在18个终端上,每个终端都有用户登录)或128-256KB RAM的PC / Home计算机时代的遗留物。在那个时候,编译器无法运行大型函数以确定要为哪个变量使用哪个寄存器以最有效地使用寄存器。因此,如果程序员使用了"register"的提示,编译器将把它放在一个寄存器中(如果可能的话)。
现代编译器不再适应2MB RAM的多次运行,但它们在分配变量到寄存器方面更加聪明。在给定的示例中,我认为编译器很难不将其放入寄存器中。显然,寄存器数量有限,对于足够复杂的代码,某些变量将无法放入寄存器中。但对于这样一个简单的例子,现代编译器将使 i 成为一个寄存器,并且它可能不会在ostream& ostream :: operator<<(ostream& os,int x)内部触及内存。

哈哈,2MB上有18个终端。离开我的草坪 :-) CP-67支持60个用户 只用了一半的资源 - jthill
是的,我的学校共享了一台运行RSTS-E的2MB PDP-11,它在我的学校有8个终端和一个打印机,在下一个学校有8+1个,在主要学校有两个房间,每个房间有16个终端(加上其他几个地方散布的一些随机终端)。那时有一个C编译器,但我们大多数人使用的是当时流行的语言Pascal。 - Mats Petersson

2
确保使用寄存器的唯一方法是使用内联汇编。但即使这样做,也不能保证编译器不会将您的值存储在内联汇编块之外。当然,您的操作系统可能会在任何时候中断程序,将所有寄存器存储到内存中,以便将CPU交给另一个进程。
因此,除非您在内核中编写具有所有中断禁用的汇编器代码,否则绝对无法确保您的变量永远不会命中内存。
当然,只有在关注安全性时才相关。从性能角度来看,使用-O3编译通常足够了,编译器通常很好地确定要保存在寄存器中的变量。无论如何,在内部循环中存储变量仅是性能调整的一个小方面,更重要的方面是确保在内部循环中不进行不必要或昂贵的工作。

0
在C++中,您可以使用volatile register int i = 10来确保i被存储在寄存器中。 volatile关键字将不允许编译器优化变量i

差得远呢。Volatile 强制编译器假定对变量的任何访问都具有可见的副作用,并且这些副作用受到强制执行,以防止由于优化而发生更改。这意味着它不能重新排序具有可见副作用的语句。 - ABaumstumpf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接