黄伟文垃圾五部曲:深入C\C++的引用,看看编译器对我们隐瞒了什么!(本实验在vs2008中进行)
来源:百度文库 编辑:九乡新闻网 时间:2024/04/27 02:19:27
许多教材书上都说,C++的引用,就是给变量取别名,即他们是同一个内存地址的两个名字。我觉得玄乎,就决定自己去看个究竟。
既然引用和变量名称是内存地址的两个名字,最直接的想法就是把他们的内存地址打印出来看看,OK,代码实验:
#include
using namespace std;
int main()
{
int i = 0;
int &j = i;
i = 1;
j = 1;
cout<<&i<<","<<&j<}
输出的结果为:0017FC04,0017FC04
果然,他们的的地址是同一个。
也许你会觉得真相已经大白,但这真的是真相吗?那么,让我们对这段代码进行反编译看看吧!为了与指针区别,我们将源代码更改如下:
int main()
{
int i = 0;
int &j = i;
int *p = &i;
i = 1;
j = 1;
*p = 1;
cout<<&i<<","<<&j<<","<<&p<}
对其进行反编译:
int main()
{
00DE3350 push ebp
00DE3351 mov ebp,esp
00DE3353 sub esp,0E8h
00DE3359 push ebx
00DE335A push esi
00DE335B push edi
00DE335C lea edi,[ebp-0E8h]
00DE3362 mov ecx,3Ah
00DE3367 mov eax,0CCCCCCCCh
00DE336C rep stos dword ptr es:[edi]
int i = 0;
00DE336E mov dword ptr ,0
int &j = i;
00DE3375 lea eax,
00DE3378 mov dword ptr [j],eax
int *p = &i;
00DE337B lea eax,
00DE337E mov dword ptr [p],eax
i = 1;
00DE3381 mov dword ptr ,1
j = 1;
00DE3388 mov eax,dword ptr [j]
00DE338B mov dword ptr [eax],1
*p = 1;
00DE3391 mov eax,dword ptr [p]
00DE3394 mov dword ptr [eax],1
cout<<&i<<","<<&j<<","<<&p< 00DE339A mov esi,esp
00DE339C mov eax,dword ptr [__imp_std::endl (0DEC390h)]
00DE33A1 push eax
00DE33A2 mov edi,esp
00DE33A4 lea ecx,[p]
00DE33A7 push ecx
00DE33A8 push offset string "," (0DE8768h)
00DE33AD mov ebx,esp
00DE33AF mov edx,dword ptr [j]
00DE33B2 push edx
00DE33B3 push offset string "," (0DE8768h)
00DE33B8 mov eax,esp
00DE33BA lea ecx,
00DE33BD push ecx
00DE33BE mov ecx,dword ptr [__imp_std::cout (0DEC394h)]
00DE33C4 mov dword ptr [ebp-0E8h],eax
00DE33CA call dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE33D0 mov ecx,dword ptr [ebp-0E8h]
00DE33D6 cmp ecx,esp
00DE33D8 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33DD push eax
00DE33DE call std:: operator<< > (0DE1339h)
00DE33E3 add esp,8
00DE33E6 mov ecx,eax
00DE33E8 call dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE33EE cmp ebx,esp
00DE33F0 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33F5 push eax
00DE33F6 call std:: operator<< > (0DE1339h)
00DE33FB add esp,8
00DE33FE mov ecx,eax
00DE3400 call dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE3406 cmp edi,esp
00DE3408 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE340D mov ecx,eax
00DE340F call dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC39Ch)]
00DE3415 cmp esi,esp
00DE3417 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
}
我了个去,好长的汇编代码,不过有汇编基础的人看懂它必然是不在话下,下面我们就来分析它,首先是初始化代码
int i = 0;
00DE336E mov dword ptr ,0
int &j = i;
00DE3375 lea eax,
00DE3378 mov dword ptr [j],eax
int *p = &i;
00DE337B lea eax,
00DE337E mov dword ptr [p],eax
乍一看,怎么引用和指针的初始化一模一样!都是将i变量单元的地址赋给它们!再看他们的赋值表达式:
j = 1;
00DE3388 mov eax,dword ptr [j]
00DE338B mov dword ptr [eax],1
*p = 1;
00DE3391 mov eax,dword ptr [p]
00DE3394 mov dword ptr [eax],1
原来引用就是不加*直接访问的是其保存的地址处的值,而指针需要加*才访问其保存的地址处的值,这一定程度上方便了我们的使用。
还有一个问题要我们解决:我们明明在第一个实验的时候,输出引用的地址和变量的地址是一样的,可是反汇编代码里,我们发现,他们占用的是不同的内存单元!这倒底是怎么回事?
带着问题我们继续往下看:
cout<<&i<<","<<&j<<","<<&p< 00DE339A mov esi,esp
00DE339C mov eax,dword ptr [__imp_std:: endl (0DEC390h)]
00DE33A1 push eax
00DE33A2 mov edi,esp
00DE33A4 lea ecx,[p]
00DE33A7 push ecx
00DE33A8 push offset string "," (0DE8768h)
00DE33AD mov ebx,esp
00DE33AF mov edx,dword ptr [j]
00DE33B2 push edx
00DE33B3 push offset string "," (0DE8768h)
00DE33B8 mov eax,esp
00DE33BA lea ecx,
00DE33BD push ecx
以上代码是输出语句的一部分,首先看
&p对应的汇编为
00DE33A4 lea ecx,[p]
这两句代码的意思是指得到p的偏移地址,同理
&i对应的汇编
00DE33BA lea ecx,
是将i的偏移地址推入栈,接下来是最关键的代码&j对应的汇编:
00DE33AF mov edx,dword ptr [j]
竟然和之前的汇编代码不一样!我们来看看他作了什么,原来他并没有获取j单元的内存地址,而是直接将j单元的值给移到了edx中,而j单元存的是i内存单元的地址,怪不得我们的代码&j得到的是i的地址,原来是这么一回事。
好了,我们来总结一下:
从表面上看,引用就是变量的别名而已,引用与变量仅仅是同一个内存单元的两个名字,就像一个人会有中文名与英文名,但都代码同一个人。
从底层的实现上看,引用其实存的是变量的地址,这些和指针一样,只是经过一系统的处理,让我们从宏观角度上看去,就是变量的一个别名。
注:以上数据仅仅是我在vs2008的运行结果,其它编译器的编译也许会存在差异,本人未曾试验。
既然引用和变量名称是内存地址的两个名字,最直接的想法就是把他们的内存地址打印出来看看,OK,代码实验:
#include
using namespace std;
int main()
{
int i = 0;
int &j = i;
i = 1;
j = 1;
cout<<&i<<","<<&j<
输出的结果为:0017FC04,0017FC04
果然,他们的的地址是同一个。
也许你会觉得真相已经大白,但这真的是真相吗?那么,让我们对这段代码进行反编译看看吧!为了与指针区别,我们将源代码更改如下:
int main()
{
int i = 0;
int &j = i;
int *p = &i;
i = 1;
j = 1;
*p = 1;
cout<<&i<<","<<&j<<","<<&p<
对其进行反编译:
int main()
{
00DE3350 push ebp
00DE3351 mov ebp,esp
00DE3353 sub esp,0E8h
00DE3359 push ebx
00DE335A push esi
00DE335B push edi
00DE335C lea edi,[ebp-0E8h]
00DE3362 mov ecx,3Ah
00DE3367 mov eax,0CCCCCCCCh
00DE336C rep stos dword ptr es:[edi]
int i = 0;
00DE336E mov dword ptr ,0
int &j = i;
00DE3375 lea eax,
00DE3378 mov dword ptr [j],eax
int *p = &i;
00DE337B lea eax,
00DE337E mov dword ptr [p],eax
i = 1;
00DE3381 mov dword ptr ,1
j = 1;
00DE3388 mov eax,dword ptr [j]
00DE338B mov dword ptr [eax],1
*p = 1;
00DE3391 mov eax,dword ptr [p]
00DE3394 mov dword ptr [eax],1
cout<<&i<<","<<&j<<","<<&p<
00DE339C mov eax,dword ptr [__imp_std::endl (0DEC390h)]
00DE33A1 push eax
00DE33A2 mov edi,esp
00DE33A4 lea ecx,[p]
00DE33A7 push ecx
00DE33A8 push offset string "," (0DE8768h)
00DE33AD mov ebx,esp
00DE33AF mov edx,dword ptr [j]
00DE33B2 push edx
00DE33B3 push offset string "," (0DE8768h)
00DE33B8 mov eax,esp
00DE33BA lea ecx,
00DE33BD push ecx
00DE33BE mov ecx,dword ptr [__imp_std::cout (0DEC394h)]
00DE33C4 mov dword ptr [ebp-0E8h],eax
00DE33CA call dword ptr [__imp_std::basic_ostream
00DE33D0 mov ecx,dword ptr [ebp-0E8h]
00DE33D6 cmp ecx,esp
00DE33D8 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33DD push eax
00DE33DE call std:: operator<<
00DE33E3 add esp,8
00DE33E6 mov ecx,eax
00DE33E8 call dword ptr [__imp_std::basic_ostream
00DE33EE cmp ebx,esp
00DE33F0 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33F5 push eax
00DE33F6 call std:: operator<<
00DE33FB add esp,8
00DE33FE mov ecx,eax
00DE3400 call dword ptr [__imp_std::basic_ostream
00DE3406 cmp edi,esp
00DE3408 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE340D mov ecx,eax
00DE340F call dword ptr [__imp_std::basic_ostream
00DE3415 cmp esi,esp
00DE3417 call @ILT+585(__RTC_CheckEsp) (0DE124Eh)
}
我了个去,好长的汇编代码,不过有汇编基础的人看懂它必然是不在话下,下面我们就来分析它,首先是初始化代码
int i = 0;
00DE336E mov dword ptr ,0
int &j = i;
00DE3375 lea eax,
00DE3378 mov dword ptr [j],eax
int *p = &i;
00DE337B lea eax,
00DE337E mov dword ptr [p],eax
乍一看,怎么引用和指针的初始化一模一样!都是将i变量单元的地址赋给它们!再看他们的赋值表达式:
j = 1;
00DE3388 mov eax,dword ptr [j]
00DE338B mov dword ptr [eax],1
*p = 1;
00DE3391 mov eax,dword ptr [p]
00DE3394 mov dword ptr [eax],1
原来引用就是不加*直接访问的是其保存的地址处的值,而指针需要加*才访问其保存的地址处的值,这一定程度上方便了我们的使用。
还有一个问题要我们解决:我们明明在第一个实验的时候,输出引用的地址和变量的地址是一样的,可是反汇编代码里,我们发现,他们占用的是不同的内存单元!这倒底是怎么回事?
带着问题我们继续往下看:
cout<<&i<<","<<&j<<","<<&p<
00DE339C mov eax,dword ptr [__imp_std:: endl (0DEC390h)]
00DE33A1 push eax
00DE33A2 mov edi,esp
00DE33A4 lea ecx,[p]
00DE33A7 push ecx
00DE33A8 push offset string "," (0DE8768h)
00DE33AD mov ebx,esp
00DE33AF mov edx,dword ptr [j]
00DE33B2 push edx
00DE33B3 push offset string "," (0DE8768h)
00DE33B8 mov eax,esp
00DE33BA lea ecx,
00DE33BD push ecx
以上代码是输出语句的一部分,首先看
&p对应的汇编为
00DE33A4 lea ecx,[p]
这两句代码的意思是指得到p的偏移地址,同理
&i对应的汇编
00DE33BA lea ecx,
是将i的偏移地址推入栈,接下来是最关键的代码&j对应的汇编:
00DE33AF mov edx,dword ptr [j]
竟然和之前的汇编代码不一样!我们来看看他作了什么,原来他并没有获取j单元的内存地址,而是直接将j单元的值给移到了edx中,而j单元存的是i内存单元的地址,怪不得我们的代码&j得到的是i的地址,原来是这么一回事。
好了,我们来总结一下:
从表面上看,引用就是变量的别名而已,引用与变量仅仅是同一个内存单元的两个名字,就像一个人会有中文名与英文名,但都代码同一个人。
从底层的实现上看,引用其实存的是变量的地址,这些和指针一样,只是经过一系统的处理,让我们从宏观角度上看去,就是变量的一个别名。
注:以上数据仅仅是我在vs2008的运行结果,其它编译器的编译也许会存在差异,本人未曾试验。
深入C\C++的引用,看看编译器对我们隐瞒了什么!(本实验在vs2008中进行)
编译器错误 C2723 (C )
Yacc: 2另一个编译器的编译器(S.C.Johnson著,中文翻译) - UNIX - 寒蝉退士
深入理解C语言指针的奥秘(1)
深入理解C语言指针的奥秘(2)
深入理解C语言指针的奥秘(3)
深入理解C语言指针的奥秘
【引用】看看兰德对国人的评价我们应该做些什么
解决VS2010自带的C/C++编译器CL找不到mspdb100.dll的问题
在gridview中把行中多余的字符用省略号代替(C#)
美国宇航局向我们隐瞒了什么?外星人就在月球背面?
勇气号最新传回的火星照片中发现建筑物(真相不能在隐瞒了)
有些话骗了我们多少年!!c
只言片语中c的人生哲理
结构体指针变量的引用--C/C++(1)
深入解析C语言声明
引用 引用 看看我们身边的造假高手们,都做了些什么!! - 过客的日志 - 网易博客
在C\\C++中嵌入汇编
[c、c++]宏中"#"和"##"的用法(zz)
对猴子进行消费行为实验:令人惊奇的一幕上演了
世界地图隐瞒了什么?
世界地图隐瞒了什么?
引用 一组漂亮的移动边框--------c
在twisted 应用中使用了多线程,怎么不能使用Ctrl C 杀死应用?