关于“C++中引用不占内存”问题的进一步解释

针对微信公众号“开点工作室”在4月16日推送的原创文章《程序员面试之必考题(一):C 中引用与指针的区别》一文,读者“像梦一样自由”提出这样的问题:“我在别的教学视频里面见到这样一个观点,引用占用内存空间,而且从汇编代码里面验证了这个引用占用内存的观点,而我们的文章中说的是引用不占用内存,所以引用到底占不占用内存,希望更进一步的解惑。”

首先感谢这位读者的关注和信任,也很赞赏他认真严谨的态度,以下是我们对该问题的阐述和解答,希望能解除读者的疑惑。,也希望更多的读者能有问题和意见可以和我们分享交流,共同学习提高。

本问题的关键是一个视角问题。同一个问题,站在C 程序员的角度与站在编译器开发者的角度来看是不一样的。

在C 中我们说“引用不占内存”是从C 程序员的角度来看。请看下列程序的运行结果。

#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
      char c;
      double i;
      double &k = i;
      char   &m = c;
      double *p = &i;
      char   *q = &c;
      cout << "Result :" << endl;
      cout << " i = " << sizeof(i) << endl;
      cout << " k = " << sizeof(k) << endl;
      cout << " c = " << sizeof(c) << endl;
      cout << " m = " << sizeof(m) << endl;
      cout << " p = " << sizeof(p) << endl;
      cout << " q = " << sizeof(q) << endl;
      return 0;
}

运行结果为:
Result :
 i = 8
 k = 8
 c = 1
 m = 1
 p = 4
 q = 4

显然,对于引用变量k来说,sizeof(k)的结果为8,sizeof(m)的结果为1,这样的结果实际是被引用对象所占内存的大小,k引用的是double类型,所以占8个字节;m引用的是char类型,所以占1个字节。这与指针变量完全不同,从运行结果可以看出,不论指针变量指向的数据类型是什么,指针变量(p、q)均占用4个字节。进一步可以用C 程序证明,对于引用变量的处理直接操作的对象就是被引用对象。所以从C 的角度来说,引用本身不会为变量开辟新的存储空间,引用只是为实际对象起了一个别名。

C 程序不能在计算机上直接运行,必须经过编译器将C 程序编译生成汇编程序,从编译的角度来看,C 编译器对引用的处理与对于指针的处理是相同的,均是为变量分配一个对应的内存空间。所以,站在编译器开发者的角度来看,在编译器中要实现引用就必须要为引用变量分配一个内存空间。

所以两个视角不能混淆。

为了更深入解释,我们可以在DEV C 环境中运行下列C 程序。

#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
       int i = 5;
       int j = 6;
       int &k = i;
       int *p = &i;
       i = j;
       k = j;
       i = k;
       &k = j;  // 编译错误,无法通过
       cout << "运行结果:" << endl;
       cout << " i = " << i << endl;
       cout << " &i = " << &i << endl;
       cout << " j = " << j << endl;
       cout << " k = " << k << endl;
       cout << " &k = " << &k << endl;
       cout << " p = " << p << endl;
       cout << " &p = " << &p << endl;
       return 0;
}
 
运行结果:
i = 6
 &i = 0x22fe98
 j = 6
 &j = 0x22fe94
 k = 6
 &k = 0x22fe98
 p = 0x22fe98
 &p = 0x22fe90

上述C 程序对应的汇编代码如下:

   0x00401576 < 86>:       mov    DWORD PTR [ebp-0x20],0x5             
 ; 语句i = 5。ebp=0x22feb8
   0x0040157d < 93>:       mov    DWORD PTR [ebp-0x24],0x6             
 ; 语句j = 6
   0x00401584 < 100>:lea    eax,[ebp-0x20]                            
 ; 语句&k = i。eax=0x22fe98
   0x00401587 < 103>:mov    DWORD PTR [ebp-0x1c],eax             
   0x0040158a < 106>:       lea   eax,[ebp-0x20]                                   
; 语句p = &i。eax=0x22fe98
   0x0040158d < 109>:mov    DWORD PTR [ebp-0x28],eax
   0x00401590 < 112>:mov    eax,DWORD PTR [ebp-0x24]             
 ; 语句i = j。eax=0x6
   0x00401593 < 115>:mov    DWORD PTR [ebp-0x20],eax
   0x00401596 < 118>:mov    edx,DWORD PTR [ebp-0x24]              
; 语句k = j。
   0x00401599 < 121>:mov    eax,DWORD PTR [ebp-0x1c]
   0x0040159c < 124>:       mov    DWORD PTR [eax],edx
   0x0040159e < 126>:       mov    eax,DWORD PTR [ebp-0x1c]             
 ; 语句i = k。
   0x004015a1 < 129>:       mov    eax,DWORD PTR [eax]
   0x004015a3 < 131>:       mov    DWORD PTR [ebp-0x20],eax
  0x004015a6 < 134>:       mov    eax,DWORD PTR [ebp-0x28]              
; 语句*p = j。
   0x004015a9 < 137>:       mov    edx,DWORD PTR [ebp-0x24]
   0x004015ac < 140>:       mov    DWORD PTR [eax],edx
   0x004015ae < 142>:       mov    eax,DWORD PTR [ebp-0x20]              
; 下一条语句
   0x004015b1 < 145>:       ……
 
通过分析可以看到,整型变量i在内存中分配的绝对地址为0x22fe98,相对地址为ebp-0x20;整型变量j的绝对地址为0x22fe94,相对地址为ebp-0x24;指针变量p的绝对地址为0x22fe90,相对地址为ebp-0x28;引用变量k在内存中相对地址为ebp-0x1c。注意:在C 程序员的视角中是无法得到引用变量k在内存中的地址。变量在内存中地址分配关系见下表。

地址                 变量           字节
ebp-0x28          p                  4
ebp-0x24           j                   4
ebp-0x20           i                   4
ebp-0x1c           k                  4
 
将C 程序与对应的汇编指令相对照。语句“&k = i”对应的汇编语句是< 100>和< 103>,编译为引用变量k分配了内存单元,且保存的是变量i的地址,语句“p = &i”的汇编语句是< 106>和< 109>,编译为指针变量p分配了内存单元,且保存的是变量i的地址,两个语句对应的汇编是一样的,都是在变量对应的单元中保存了相关对象的地址。

语句“k = j”对应的汇编语句是< 118>、< 121>和< 124>,语句“*p = j” 对应的汇编语句是< 134>、< 137>和< 140>,两相对照,生成的汇编指令没有本质区别,可以认为是完全等价的。因此可以得出结论,通过编译之后,生成的最终代码在汇编级对于引用变量和指针变量的处理是相同的。

在C 的视角来看,引用变量与指针变量是不同的。指针变量是一个实体,在运行过程中可以改变;而引用仅是某个变量的别名,引用变量在被创建的同时必须被初始化,且在运行过程中不能被再次改变。

在我们给出的上述源程序示例中,语句“&k = j;”是无法通过编译。因此可以认为,C 语言中对于引用型变量的限制是由编译器本身限制的。

横扫offer---程序员招聘真题详解700题》,开点工作室著,清华大学出版社出版,天猫、京东等各大网上书店及实体书店均已开始发售。
全部评论

相关推荐

03-28 19:11
铜陵学院 C++
有礼貌的山羊追赶太阳:太典了,连笔试都没有开始就因为HC满了而结束了,而且还卡你不让你再投其他部门的。
点赞 评论 收藏
分享
04-25 18:13
五邑大学 Java
后来123321:大二两段实习太厉害了,我现在大二连面试都没有
点赞 评论 收藏
分享
评论
点赞
7
分享

创作者周榜

更多
牛客网
牛客企业服务