c++ 引用底层实现

红色是我添加的,其余地方是原做者的。ios

主要是看了上面的这篇“从底层汇编理解 c++ 引用实现机制“的文章以后,以为不错。就转了过来,同时,对文中的程序都在本身的机器上验证了一下。c++

使用的G++版本:g++ (GCC) 4.5.1 20100924程序员

若是要查看汇编后代码与源码的关系,我用的方法是:面试

先用g++生成带有调试信息的目标文件:g++ -g -c ref.cc函数

而后再利用objdump命令查看目标文件ref.o:objdump -S ref.ospa

引用类型究竟是什么?它和指针有什么关系?它自己占用内存空间吗? 带着这些疑问,咱们来进行分析。 先看代码:.net

  1. #include <stdio.h>  3d

  2. #include <iostream>  指针

  3.   

  4. using namespace std;  调试

  5.   

  6.   

  7. void main()  

  8. {  

  9.    int x = 1;  

  10.    int &b = x;  

  11.  }   

int main()

{

int x=1;

int &b=x;

return 0;

}

  经过汇编查看代码以下:



  1. 9:       int x = 1;  

  2. 00401048   mov         dword ptr [ebp-4],1  

  3. 10:      int &b = x;  

  4. 0040104F   lea         eax,[ebp-4]  

  5. 00401052   mov         dword ptr [ebp-8],eax  

00000000 <main>:

int main()

{

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 ec 10 sub $0x10,%esp

int x=1;

6: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp)

int &b=x;

d: 8d 45 f8 lea -0x8(%ebp),%eax

10: 89 45 fc mov %eax,-0x4(%ebp)

return 0;

13: b8 00 00 00 00 mov $0x0,%eax

}

18: c9 leave

19: c3 ret



  能够知道x的地址为ebp-4,b的地址为ebp-8,由于栈内的变量内存是从高往低进行分配的。因此b的地址比x的低。
  lea eax,[ebp-4]  这条语句将x的地址ebp-4放入eax寄存器

  mov dword ptr [ebp-8],eax 这条语句将eax的值放入b的地址ebp-8中

  上面两条汇编的做用即:将x的地址存入变量b中,这不和将某个变量的地址存入指针变量是同样的吗?

   因此从汇编层次来看,的确引用是经过指针来实现的。

   下面咱们经过程序来验证,咱们知道,在程序一层咱们只要直接涉及到引用变量的操做,咱们操做的老是被引用变量,即编译器帮咱们作了些手脚,老是在引用前面加上*。因此咱们要读取真正的“引用变量的值”,必须采起必定的策略,好吧,咱们就按照变量在栈中分布的特色来绕过编译器的这个特色。

[cpp] view plaincopyprint?

  1. #include <stdio.h>  

  2. #include <iostream>  

  3.   

  4. using namespace std;  

  5.   

  6. void main()  

  7. {  

  8.    int x = 1;  

  9.    int y = 2;  

  10.    int &b = x;  

  11.    printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));  

  12.  }   

  输出结果为:&x=12ff7c,&y=12ff78,&b=12ff74,b=12ff7c

#include <cstdio>

 

int main()

{

int x=1;

int y=2;

int &b=x;

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

return 0;

}

输出结果是:

&x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460

 

这个地方的结果和做者不同,能够看后面的解释。





  1. void main()  

  2. {  

  3.    int x = 1;  

  4.    int &b = x;  

  5.    printf("&x=%x,&b=%x\n",&x,&b);  

  6.  }   

  输出结果为:&x=12ff7c,&b=12ff7c.

#include <cstdio>

 

int main()

{

int x=1;

int &b=x;

printf("&x=%x,&b=%x\n",&x,&b);

return 0;

}

输出结果:&x=bfe74aa8,&b=bfe74aa8



  b的地址咱们无法经过&b得到,由于编译器会将&b解释为:&(*b) =&x ,因此&b将获得&x。也验证了对全部的b的操做,和对x的操做等同。

  可是咱们能够间接经过&y-1来获得b的地址,从而获得b的值:*(&y-1)  从结果能够知道,b的值即x的地址,从而能够知道,从地层实现来看,引用变量的确存放的是被引用对象的地址,只不过,对于高级程序员来讲是透明的,编译器 屏蔽了引用和指针的差异。

  下面是程序的变量在内存栈中的分布,引用变量同样也占用内存空间,并且应该是4个字节的空间。

   虽然从底层来讲,引用的实质是指针,可是从高层语言级别来看,咱们不能说引用就是指针,他们是两个彻底不一样的概念。有人说引用是受限的指针,这种说法我不赞同,由于从语言级别上,指针和引用没有关系,引用就是另外一个变量的别名。对引用的任何操做等价于对被引用变量的操做。从语言级别上,咱们就不要去考虑它的底层实现机制啦,由于这些对你是透明的。因此在面试的时候,若是面试的人问到这个问题,能够先从语言级别上谈谈引用,深刻的话就从底层的实现机制进行分析。而不能什么条件没有就说:引用就是指针,没有差异,......之类的回答

 

对于下面的程序:

#include <cstdio>

 

int main()

{

int x=1;

int y=2;

int &b=x;

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

return 0;

}

个人结果是&x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460

与原做者的不一样,而后我就将上面的程序进行编译获得下面的结果:

00000000 <main>:

#include <cstdio>

 

int main()

{

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 e4 f0 and $0xfffffff0,%esp

6: 83 ec 30 sub $0x30,%esp

int x=1;

9: c7 44 24 28 01 00 00 movl $0x1,0x28(%esp) 1赋值给x(x在堆栈的0x28处)

10: 00

int y=2;

11: c7 44 24 24 02 00 00 movl $0x2,0x24(%esp) 2赋值给y(y在堆栈的0x24处)

18: 00

int &b=x;

19: 8d 44 24 28 lea 0x28(%esp),%eax x的地址0x28 传给寄存器%eax

1d: 89 44 24 2c mov %eax,0x2c(%esp) %eax的值赋给堆栈0x2c处(这儿比较重要)

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

21: 8d 44 24 24 lea 0x24(%esp),%eax 将堆栈0x24处的地址传给寄存器%eax

25: 83 e8 04 sub $0x4,%eax %eax的值减掉4

28: 8b 10 mov (%eax),%edx 将寄存器%eax中地址所指向的内容传给寄存器%edx

2a: 8d 44 24 24 lea 0x24(%esp),%eax 将堆栈0x24处的地址传给寄存器%eax

2e: 83 e8 04 sub $0x4,%eax %eax的值减掉4

31: 89 54 24 10 mov %edx,0x10(%esp) %edx的内容传给堆栈0x10处

35: 89 44 24 0c mov %eax,0xc(%esp) %eax的内容传给堆栈0xc处

39: 8d 44 24 24 lea 0x24(%esp),%eax 将堆栈0x24处的地址传给寄存器%eax

3d: 89 44 24 08 mov %eax,0x8(%esp) %eax的内容传给堆栈0x8处

41: 8d 44 24 28 lea 0x28(%esp),%eax 将堆栈0x28处的地址传给寄存器%eax

45: 89 44 24 04 mov %eax,0x4(%esp) %eax的内容传给堆栈0x4处

49: c7 04 24 00 00 00 00 movl $0x0,(%esp)

50: e8 fc ff ff ff call 51 <main+0x51>

return 0;

55: b8 00 00 00 00 mov $0x0,%eax

}

5a: c9 leave

5b: c3 ret

 

上面基本每一句中都进行解释。

从这儿能够看到个人机器上生成的汇编代码是将x的地址赋给了堆栈中地址所在处的下一个地址单元。

从printf所生成的汇编代码处咱们也能够看到,是按照逆序来计算的(&*(&y-1), &y-1, &y, x)这也印证了C标准中提到的函数的参数是逆序入栈的。

为了验证上面的想法,将原程序中的*(&y-1)改成*(&x+1)

结果为:

&x=bf9a74c8,&y=bf9a74c4,&b=bf9a74cc,b=bf9a74c8

这样就和做者的符合起来了。

相关文章
相关标签/搜索