转自探索c++的底层机制

在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么你休想构造出任何可执行程序来。但如果真正到了产生可执行代码阶段,无论是c,c++,还是pascal,大家都一样,你认为c和c++编译器产生的机器代码会有所不同吗,你认为c++产生的机器代码会有访问限制吗?那么你错了。什么const,private,统统没有(const变量或许会放入只读数据段),它不会再给你任何的限制,你可以利用一切内存修改工具或者是自己写一个程序对某一进程空间的某一变量进行修改,不管它在你的印象中是private,还是public,对于此时的你来说都一样,想怎样便怎样。另外,你也不要为c++所提供的什么晚期捆绑等机制大呼神奇,它也仅仅是在所产生的代码中多加了几条而已,它远没有你想象的那么智能,所有的工作都是编译器帮你完成,真正到了执行的时候,计算机会完全按照编译器产生的代码一丝不苟的执行。你明白我在说什么吗?对了,如果你从前接触过汇编,只要你反汇编一段c++代码,你就会说:原来是这么回事呀,c++只不过是把我们的问题进行了更高层次的抽象,但只要你解开面纱,回到问题的本源,一切都将变得不再神秘……

(以下的反汇编代码均来自visial c++ 7.0) 

一.让我们从变量开始-----并非你想象的那么简单

变量是什么,变量就是一个在程序执行过程中可以改变的量。换一个角度,变量是一块内存区域的名字,它就代表这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。但是你若是学习过汇编或是计算机组成原理,那么你就会清楚对于一块内存区域来说,根本就不存在什么名字,它所仅有的标志就是他的地址,因此我们若想修改一块内存区域的内容,只有知道他的地址方能实现。看来所谓的变量一说只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维跨度而已。例如下面这条语句:

int a=10;

按照我们的思维习惯来讲,就是“存在一个变量a,它的值是10”,一切都显得那么的自然。我们不必去在乎什么所谓的地址以及其他的一些细节。然而在这条语句的底层实现中,a已经不能算是一个变量了,它仅仅是一个标记,代表一个地址的标记:

mov dword ptr[a],0Ah;

怎么样,这条语句不像上面那条易于接受吧,因为它需要了解更多的细节,你几乎不能得到编译器的任何帮助,一切思维上的跨越必须由你自己完成。这条语句应该解释为“把10写入以a为地址的内存区域”。你说什么?a有些像指针?对,的确像,但还不是,只不过他们的过程似乎是类似的。这里所说的跨越实际上就是从一个现实问题到具体地址以及内存区域的跨越。

二.引用:你可以拥有引用,但编译器仅拥有指针(地址)

看过了第一条,你一定对编译器的工作有了一定的了解,实际上编译器就是程序员与底层之间的一个转换层,它把一个高级语言代码转换为低级语言代码,一个编译器完成的转换跨度越大,那么它也就会越复杂,因为程序员的工作都由他代为完成了。C++编译器必然比汇编编译器复杂就是这个道理。如果我问你引用和指针是一样的吗?你或许会说当然不一样了,指针容易产生不安全的因素,引用却不会,真的不会吗?我们来看下面这段代码:

int *e=new int(10);
int &f=*e;
delete e;
f=30;

你认为上面这段代码怎么样,我感觉就不很安全,它和指针有相同的隐患。因为它所引用的内存区域就不合法。

我个人认为,所谓的引用其实就是一种指针,只不过二者的接口并不相同,引用的接口有一定的限制。指针可以一对多,而引用却只能一对一,即&refer不能被改变,但却并不能说一对一就是安全的,只不过危险的系数降低罢了。引用比指针更容易控制。

Ok,下面来说说指针,曾经有过汇编经验的人一定会说,恩,指针的某些地方有些像汇编,尤其是那个“*”,怎么就那么像汇编中的“[]”啊。呵呵,的确,它也涵盖了一个寻址的过程。看来指针的确是个比较低级的东西。然而引用却并不那么直接,虽然程序员用起来方便安全了许多。但是你要清楚,只有你可以拥有引用,编译器可没有这个工具,计算机并不认识这个东西。因此,它的底层机制实际上是和指针一样的。不要相信只有一块内存拷贝,不要认为引用可以为你节省一个指针的空间,因为这一切不会发生,编译器还是会把引用解释为指针。不管你相不相信,请看下面这段代码:

int& b=a;
lea eax,[a];
mov dword ptr[b],eax;把a的地址赋给地址为b的一块内存 b=50;
mov eax,dword ptr[b];
mov dword ptr[eax],32h; int *d=&a;
lea eax,[a];
mov dword ptr[d],eax *d=60;
mov eax,dword ptr[d]
mov dword ptr[eax],3ch;

以上的代码均来自具体的编译器,怎么样,相信了吧,好,让我再来做一个或许不怎么恰当的比拟,你一定编过有关线性表和栈的程序吧,线性表是一个非常灵活的数据结构,在他上面有许多的操作,然而栈呢,它是一个限制性操作的线性表,它的底层操作实际上是由线性表操作实现的。就好比stack与vector的关系,因此指针和引用的关系就好比线性表和栈的关系,引用也就是受限的指针,它对外的接口和指针虽然并不一样,但底层是相同的。

下面再来看看引用的一个重要用途,作为函数的参数传递的时候是怎样的情形:

void swapr(int &a, int &b);
void swapr(int* a, int *b); int a=10;
int b=20; swapr(a, b);
lea eax,[a];
push eax; //把a的地址压入堆栈
lea ecx,[b];
push ecx;
call swapr; swapr(&a, &b);
lea eax,[a];
push eax;
lea ecx,[b];
push ecx;
call swapr;

怎么样,用引用和指针传递参数无论是在效率上还是在空间上都是完全一样的,如果妄想不传入地址就修改实参的值,简直就是天方夜谭,这就说明引用的本质就是指针。毕竟它们的行为都太相似了,如果不是这样,你还有什么方法去实现引用吗?记住,引用只不过是编译器为你提供的一个有用且安全的工具,对于机器代码可无法表示它,它把指针一对多的缺点去除,禁止了你的不安全的操作。但回到问题的本源,他们没有任何区别。

C++引用的实质的更多相关文章

  1. C++中引用的本质

    一般的教材上讲到引用时,都是说"引用是对象的一个别名".我认为这种定义是不清晰的,不利于初学者理解引用.至少我自己曾经被这个定义困扰了一段时间.到底什么是"别名" ...

  2. C++中引用的本质是什么?

    一般的教材上讲到引用时,都是说“引用是对象的一个别名”.我认为这种定义是不清晰的,不利于初学者理解引用.至少我自己曾经被这个定义困扰了一段时间.到底什么是“别名”? 实际上,引用的实质是位于xxxxx ...

  3. c++学习笔记(c++中的引用)

    1.c++中的bool类型:     其实c语言中也有bool类型,如果是遵守c90标准的编译器(其实现在大量编译器都是c90标准的),对于bool类型的使用除了要使用头文件 stdbool.h外,与 ...

  4. Java 对象 引用,equal == string

    以前确实一直没注意这个概念,这次看了帖子才知道. 转载于:https://zwmf.iteye.com/blog/1738574 Java对象及其引用 关于对象与引用之间的一些基本概念. 初学Java ...

  5. Swift5 语言指南(二十五) 自动引用计数(ARC)

    Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...

  6. c++ 引用底层实现

    红色是我添加的,其他地方是原作者的. 主要是看了上面的这篇“从底层汇编理解 c++ 引用实现机制“的文章之后,觉得不错.就转了过来,同时,对文中的程序都在自己的机器上验证了一下. 使用的G++版本:g ...

  7. 010. C++ 传值与传引用

    1.参数传递 参数传递:pass by value vs. pass by reference(to const) 推荐:能传引用,尽量传引用(高效,尤其在需要拷贝的对象很大时) class comp ...

  8. 解决老大难疑惑:指针 vs 引用

    ▶疑问描述 1.  引用reference的本质: 常指针 ——> 什么时候用指针?= 就按Java中的引用变量那样用? ——> 什么时候用引用?  ①函数的入参/返回值时   ②T&am ...

  9. 转发:Java对象及其引用

    原文: http://zwmf.iteye.com/blog/1738574 Java对象及其引用 关于对象与引用之间的一些基本概念. 初学Java时,在很长一段时间里,总觉得基本概念很模糊.后来才知 ...

随机推荐

  1. Redis的配置

    Redis是一个强大的Key-Value存储系统,在前面我们已遇到了两个问题: 1.redis server 启动后,独占进程,能不能修改为后台服务呢? 2.redis server 服务是单线程的, ...

  2. HTML5 服务器发送事件

    单向传输:服务器端——>客户端   作用:传回的能每过3s重新刷新一遍.从而能过跟数据库同步,与ajax配合使用   一.客户端写法 必须的用 message 方法   JSON.parse() ...

  3. 第四章 jQuery中的事件

    1.加载DOM jQuery中,在$(document).ready()方法内注册的事件,只要DOM就绪就会被执行,此时可能元素的关联文件未下载完. jQuery中的 load()方法,会在元素的on ...

  4. Android——简单音乐播放器

    使用MediaPlayer做的简单音乐播放器,更多内容请到百度经验查看   http://jingyan.baidu.com/article/60ccbceb63452364cab197f1.html ...

  5. 利用sys.dm_db_index_physical_stats查看索引碎片等数据(转)

    我们都知道,提高sql server的数据查询速度,最有效的方法,就是为表创建索引,而索引在对数据进行新增,删除,修改的时候,会产生索引碎片,索引碎片多了,就需要重新组织或重新生成索引,以达到索引的最 ...

  6. Windows7 下安装ORACLE 11G(遇到的问题)

    首先官网下载ORACLE11G(我的电脑是32位) 下载到磁盘后(解压成为一个文件有个DATABASE文件夹) 点击安装 只安装数据库软件(之后再创建数据库:因为容易出问题) 之后的安装过程就跟着走就 ...

  7. ###《Effective STL》--Chapter7

    点击查看Evernote原文. #@author: gr #@date: 2014-08-31 #@email: forgerui@gmail.com Chapter7 在程序中使用STL Topic ...

  8. 循环/loop 结构/structure

    1.Shell loop 2.C++/CPlusPlus ①.std::for_each ②.for loop ③.Iterator library 3.Python Loop ①.Python.or ...

  9. Unity中使物体自动寻路的方法

    在做一个FPS游戏时,需要敌方自动找到玩家方位并向玩家移动,在查找资料(并走了不少坑)后,我试了三个方法,经测试,这三个方法都能实现自动寻路功能. 方法一:使用Mathf.Lerp()方法 代码很简单 ...

  10. java中的IO流

    Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...