疑问:
1. 引用在初始化的时候赋值的,后面可以直接使用该引用,那么该引用是一个变量吗?
2. 如果是变量,那么肯定是有内存的?内存空间占用多少呢?里面存放的是变量的地址?还是变量的值?
3. 如果不是变量,在编译的时候应该是一个单独的符号,后面使用该符号的时候,怎么就知道使用哪里的值?怎么绑定的呢?
4. C++ pp 书中将其称为“别名”,或许如上面第3点,仅仅是变量的第二种符号?
通过实例进行深入分析
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int &j = i;
i = 1;
j = 2;
cout << "i: " << i << ", j: " << j << endl;
return 0;
}
作为使用应用很六六的本人来说,i、j 打印出来的肯定是2(尴尬的自我感觉良好)。
带着结果去编译下,来看下符号表:
尴尬的是并没有看到 i 和 j 的符号,但是记得以前一本书上看过,即使是局部变量也是有符号的。
为了看到这个绑定关系,确认是否是前言中第3点猜想,还是要想办法看这个符号表的,把局部变量弄成全局就好。
代码稍微调整下:
#include <iostream>
using namespace std;
int i = 10;
int &j = i;
int main()
{
i = 1;
j = 2;
cout << "i: " << i << ", j: " << j << endl;
return 0;
}
重新来看符号表:
引用是独立存在的,有自己的内存空间,而且引用占用空间跟int 型的变量 i 还不一样,i 占用的是4个字节大小,而 j 占用了8个字节(这个看系统了,64位系统的地址是8字节的,这里别乱了)。
回到原来的代码,用gdb 来看看内存情况:
打印出来i、j 的值都是2,这个毋庸置疑的,但奇怪的是为什么&j 的地址和&i 的地址是一样的呢?
暂时先不管这个疑问吧,来看下代码运行:
介绍下rsp 和rbp:
%rbp
is the base pointer, which points to the base of the current stack frame, and%rsp
is the stack pointer, which points to the top of the current stack frame.%rbp
always has a higher value than%rsp
because the stack starts at a high memory address and grows downwards.
回到代码,stack 上申请了0x20 大小的空间,-0x24 为i 的内存,-0x20为 j 的内存。
这里可以看到 j 占用的是-0x20 的内存,里面存放的是 i 的地址。
这里也解释了为什么符号表中看到的 j 的内存大小是 8 个字节了,系统是64位的。
来看下内存情况:
&i 和 &j 是一样的,上面已经确认了,即-0x24 对应就是内存地址0x7fffffffe20c,那么向上偏移4个字节就是 j 的内存,里面确认存放的就是 i 的地址。与上面的代码对应上了。
再来看下对 j 赋值:
从代码看,是先获取了 j 内存空间的值,也就 i 地址,然后修改 j 内存空间的值所指向的地址的值。
可以猜想下,编译器可以这样的解析引用:
int* const k = &i;
j 等同于 *k,使用j 时,其实是使用 *k,即:
j = 2;
等同于:
*k = 2;
那么&j 可以等同于&*k,而&* 抵消,&j = k = &i;
这就解释了为什么打印出来 j 的地址和 i 的地址是一样的。
其实通过上面代码可以看出,引用的实质就是一个指针常量,因为是指针常量,所以必须要在初始化的时候赋值,所以不允许后面进行更改。而这些是编译器已经做好了,才会有上面的汇编代码。
如上面代码,k 为指针常量,k 的内存空间应该是在常量区,k 的内存里存放着 i 的地址,j 相当于 *k,即使对指针常量的“引用”,指向了确切的变量。
总结:
- 引用在使用的时候可以直接看成变量的别名,当做变量直接使用;但实质上是指针常量;
- 引用实质是一个指针常量,所以需要在定义的时候初始化,后面是不允许改变的;
- 引用有自己的内存空间,空间大小就是初始化时变量的地址的大小;
参考:
Understanding C by learning assembly - Blog - Recurse Center