环境:
操作系统:Ubuntu 18.04.3 LTS
架构:Aarch64
内核:Linux 4.4.167
机器型号:RK3399
CMake:3.10.2
GCC:Ubuntu/Linaro 7.5.0-3ubuntu1~18.04
G++:Ubuntu/Linaro 7.5.0-3ubuntu1~18.04
GDB:GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Make:GNU Make 4.1
1、说在最前面
以前学C语言的时候老师介绍过寄存器变量,是保存在CPU寄存器中的(寄存器一般很小,价格比黄金贵多了),方法就是使用register修饰符来修饰变量,例如:register int i = 0;但众所周知这样也不能保证当前变量一定会保存在寄存器里面(但这只是建议性的,具体和硬件与操作系统实现有关),具体实现原理这里不深入推敲感兴趣的可以深入研究下。本篇文章会带入一个概念“取地址操作”也就是C语言和C++里面的&符号(C++也代表引用,不要搞混了,这里不详述区别)。取地址操作只有针对保存在内存里面的变量才有效,也就是我们所说的普通变量,相对应的寄存器变量是没有取址操作的。C语言会直接报错,C++可以编译也可以运行,这个放在最后说。上代码:
2、测试代码
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
register int i = 0;
// cout << &i << endl;
// int i = 0;
for (int j = 0; j < 10000000; ++j) {
i += j;
}
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}eturn 0;
}
以上代码就是创建一个寄存器整型变量,简单地对其做一千万次累加,累加次数越多差距越明显。运行多次取平均值:0.0702972,单位秒。
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
// register int i = 0;
// cout << &i << endl;
int i = 0;
for (int j = 0; j < 10000000; ++j) {
i += j;
}
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}
以上代码是创建一个普通变量,简单地对其做一千万次累加,累加次数越多差距越明显。运行多次取平均值:0.102241,单位秒。
接下来我们进行一个特殊操作,先对寄存器变量进行取址操作再进行累加
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
register int i = 0;
cout << &i << endl;
// int i = 0;
for (int j = 0; j < 10000000; ++j) {
i += j;
}
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}
结果打印了0x7fe64b2df0,时间:0.110934,是不是很奇怪难道是以前学的知识有误,寄存器怎么会取出来地址。答案是寄存器没有地址,不能进行取址操作,以前学的知识也没有问题。那么问题出在哪里呢?原因是C++对寄存器变量取址操作做了处理,只要你对寄存器变量做了取址操作就会把寄存器变量变成一个普通变量,而且过程是不可逆的。也就是说在取址操作之前它有可能是寄存器变量。
3、疑问
很多人肯定有疑问,怎么知道声明的寄存器变量是不是真的存在了寄存器里面呢?我翻看了很多人的博客(当然不是所有人的博客),很多博主和我有同样的疑问,是不是真的一点办法都没有,答案是否定的我觉得一个方法可行那就是通过gdb。通过查看实际存在的寄存器变量来判断你声明的变量是不是被系统放进了寄存器。话不多说,上图!
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
register int i = 123456789;
cout<<i<<endl;
// cout << &i << endl;
// int i = 123456789;
// for (int j = 0; j < 10000000; ++j) {
// i += j;
// }
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}
这里为了方便区分我给寄存器变量附了一个我认为唯一的又没有特殊意义的值123456789,用来在调试器里面定位他。
上gdb调试,在return 0 之前寄存器变量声明之后打上断点,点击gdb窗口。输入info registers 查看所有寄存器变量
仔细看红框框出来的那行,那就是我们申请的寄存器变量,他被操作系统接受了。可能很多人会说那么多变量你怎么确定那个就是你声明的变量,在这里我暂时没有办法,目前你只能尽量弄一个不太可能和操作系统重叠的变量用来标记。接下来我们声明一个普通变量试一试。
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
// register int i = 123456789;
// cout << &i << endl;
int i = 123456789;
cout<<i<<endl;
// for (int j = 0; j < 10000000; ++j) {
// i += j;
// }
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}
还是同样的值,同一个操作,只是这次没有register声明。这个图没有截取完,我翻了一遍没有发现那个值。接下来我们试试在声明register变量后取址看看前后的变化。
#include <iostream>
#include <chrono>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
auto start = chrono::steady_clock::now();
register int i = 123456789;
cout<<i<<endl;
cout << &i << endl;
// int i = 123456789;
// for (int j = 0; j < 10000000; ++j) {
// i += j;
// }
auto end = chrono::steady_clock::now();
chrono::duration<double> elapsed = end - start;
cout << "register cost:" << elapsed.count() << endl;
return 0;
}
寄存器变量又回来了,存在寄存器的不同位置但是是同一个无意义值,接下来取址后到下一个断点。
寄存器变量不见了,当然不是彻底消失了而是去了内存里。 接下来我们使用 x /1xt <address>来获取这个变量在内存中的值,看看是不是我们定义的那个值,首先拿到我们打印的内存地址0x7ffffffa94使用gdb调试指令来获取32位的二进制代码。
看到了没有一模一样的值,肯定不是凭空产生的。
4、写在最后
1、寄存器变量比内存变量快,但是具体实现是操作系统+硬件决定的
2、C++可以对寄存器变量进行取址操作,只不过取址操作之后寄存器变量成了普通变量,而且过程不可逆转。
3、C++处理寄存器变量和普通变量的转换是在运行阶段,不是编译阶段。
4、欢迎大家给出批评意见