C++内存的理解

By | 02月26日
Advertisement

内存可以说是C和C++语言学习的关键点。
这里说一点我的理解,一家之言,欢迎拍砖哈。

内存要想理解透彻,首先要理解内存编址。即不同的内存条,内存模块,插到机器上,具体对应的内存地址是多少。

最开始的PC机,IBM PC XT,只有640k内存。IBM是这么规划的,最低的128k,是BIOS的地址,毕竟BIOS也是汇编语言,它也需要合法地址,才能被CPU正确运行。

512k~640k,被定义为端口映射地址,即这部分地址,可能对应某一个外设上的地址,方便程序直接访问设备。其中,最重要的,就是显卡,当初的显卡单元都比较小,如单色显卡只有2k显存,就映射在这个地址段。

呵呵,当年比尔盖茨,开了个世纪有名的黄腔,就是家用电脑,640k内存足以。就是这么来的,可现在呢?你的显存都不止640k,可见,伟人也有犯浑的时候。

这样显然不方便,因为就在几年后,286时代,内存已经1M了。
这就麻烦了,这1M的第一个128k不准用,中间还有个128k不准用,好端端的连续内存,就被切成两块,痛苦啊。

而那会CPU也是笨得可以,16位的CPU,Intel居然在PC XT上面用的是8位地址总线,这下死翘了,所有的内存被分割为64k一块块的小块,叫段。每个程序模块,必须小于64k,否则没法跳转。以前DOS下可以 执行的二进制文件分为两种,com和exe,com就是不能大于64k的,因为它文件格式里面没有段修饰,因此,只能一段完成,64k。

这样,程序员十分的不方便,写程序,稍微大点的数组,就要考虑分段访问,没办法,数组下标不能超过64k。
另外,对于640k以上的地址访问,人们想出了一个很笨的方法,把640k~1024k这384k,也切成一块一块的,每块映射相同的内存区域,靠一个IO切换来访问不同的块。那会还没有想到更好的支持1M以上内存的方法,只能这么办。

这样,程序员成了个苦恼的职业,既要做软件编程,还要随时关注自己的数据是否越界,痛苦死了。当然,程序员也不会坐以待毙,这期间,他们自己想了很多方法,比如用底层模块来解决段切换问题,对上提供一层连续编址的虚拟地址来访问等等。

ok,到了80386,32位了,大家总算长出了一口气,这个CPU地址总线有32位,可以直接编址4G内存。
但是,问题来了,PC机已经做成这个样子了。当然,可以重新开发一款计算机,连续编址,4G内存,很爽,只是,这不是PC机了。

这又让人痛苦死了,明明有能力做连续编址,跨越段界限,但还是不能这么做,因为要支持老的程序。

程序员又开始想办法,基本思路就是用虚拟地址来代替实际地址,后来又想到了,既然都是虚拟的,那我们可不可以把一块磁盘文件也虚拟成内存,这样,我们给够4G,这不是更爽,底层再用一定算法来处理动态转换的效率优化。

这样,在93年左右吧,有一个很有名的C++语言,Watcom C++出世了。这个语言,是Dos下第一款支持4G内存的C++语言。这是Sybase开发的,它底层使用了一个虚拟内存管理模块,是另外一家公司开发 的,叫Dos /4G,显然,就是DOS程序使用4G内存的解决方案。

还记得DOOM、C&C,红警,金庸群侠传不?当程序一运行,就会显示一行字“DOS /4GW …”,这就是Watcom C++写的游戏。因为游戏界是最需要大内存的,贴图,声音的处理,都需要大数组,如果分块使用,程序员太累了,做不下来这么大的程序。

嗯,DOS/4GW,是DOS 4G的Watcom C++版本,因为真正的模块要收费的,这个版本是简化版,只能支持到256M内存,且不支持磁盘虚拟内存,不过不收费。不过,那个时代的应用也够了。

我以前写过Watcom C++的程序,呵呵,真的很爽。再也不考虑段指针之类的东东了,爽翻了。

后来就多了,gcc很早就有了的,99年的时候,gcc进攻DOS市场,出了个版本叫DJgpp,比Watcom C++还好用,我一用就爱上了,当时还把它的库函数手册翻译了一遍,算学习了。

不过,这个时间点,Windows95早出来了,Windows98也出来了,因此,DOS程序趋于没落。

早在Windows 1.1开发的时候(最早一个Windows版本,1.0没发布),微软就知道,以后的操作系统要做到程序员友好才有生命力,而内存连续编址,就是最大的程序员友好。

因此,从一开始,Windows就使用了底层的内存支持技术,当Windows 3.1出世,其实Windows下开发程序,段的限制已经不是很明显了。

到Windows95,微软更是直接内置了类似于Dos /4GW之类的32位内存管理器,并内部直接包含虚拟内存和物理内存的自动切换算法,因此,从Windows 95以后,大家再开发程序,已经可以使用理论上长达4G的大数组了。

内存争议,至此告一段落。

现今32位的Windows系统,普遍支持4G内存,但,应用程序的空间只有2G。编址为低端地址,即0~2G的地址,为什么呢,因为高2G被系统占用,毕竟Windows系统那么多服务,也要运行,也需要地址空间。

Windows使用了类似切页的内存控制机制,每个应用程序,有2G的地址空间,上面2G是所有应用程序和系统共用。
呵呵,不止Windows,32位的Linux也有类似的设计。
因此,一个Windows应用程序最大能使用的内存,只有2G。

前面说的Ring0级的系统内核,一般都占用上2G的地址空间在运行。动态链接库dll,控件OCX,在调入内存中,由于要被多个进程看到,进程间重用,也是占用上面2G在运行。

这里就要说说钩子了,当我们想对一个应用程序下钩子,由于我们的程序和要勾的程序不在一个进程空间,因此,我们看到的内存是不一样的。就是它在20000 这个地址单元看到的可能是个FF,而我们看到的可能是个00,因为这仅仅是逻辑地址一样,物理地址分属两个进程空间。
因此,如果要钩对方的消息,有个问题,钩到了,咋送回来?
一般的做法就是做个dll做中转站,钩子够到了,调用dll的函数,先存放到高端内存区,然后我们的程序再定时过去取,或者用回调什么的。
总之,如果要跨进程通讯,两个进程的共享内存区,一定是建立在高端的内存,就是2G以上的空间。

至于应用程序自己这2G,就看编译器咋使用了,一般都是,低端为栈空间,我们的函数代码,每次call一个函数,函数内部新建立的内部变量,是从低端向高处排。
而堆,则是从高处向底处排,啥时候,两个碰上了,啥时候,内存就满了,无法申请内存了。

栈又分为基栈和浮动栈,基栈就是编译期间就分配好了的内存。
全局变量,const的常量,static的变量,函数的代码,都是这部分。
浮动栈就是运行期间,根据函数,对象调用关系,动态分配的栈,类成员变量,函数内部变量,都是用的浮动栈。

void Func(void)
{
char i=0;
char* pBuffer=malloc(10);
//…
}
这里面,Func的代码,在栈空间,其实是在基栈了。2G的最低
int i,这个i,在浮动栈。基站上方,也还是2G底部。
pBuffer指向的内存,由于是malloc,因此是堆空间,在2G的高端。

new和malloc其实是一样的,都是malloc的,但new支持对象,要自动调用构造函数。
不过这里也说明一点,new出来的对象,是运行期对象,其内部成员变量,其实是在2G的高端,堆空间里面。

写C和C++程序,需要对内存的分配非常敏感,随时关注自己使用的变量,是属于编译期间的基栈,还是运行期的浮动栈,还是堆。

比如,我们要启动一个线程,这个线程函数,肯定是在基栈了,编译器就定好的,但是我们希望线程访问一个运行期的动态地址,比如要传递一个参数给它。
我们就不能简单把一个函数内部的变量地址传给他,由于是函数内部变量属于浮动栈,函数返回,浮动栈就自动拆除,而线程启动是异步运算,就是一个函数启动线程,很可能这个函数已经返回了,线程还没有开始运行。
因此,就绝对不能使用函数内部变量给线程传参,只能使用堆空间,用malloc的一块地址来传参数,再由线程函数收到后,free掉。

这是唯一一个,不遵守“谁分配,谁释放”原则的特例。我把它叫做“远堆传参”。
很多初学线程的朋友,线程写出来就挂掉,就是这个地方出了问题。
但是,程序表面看起来一切正常,呵呵,所以内存很重要,因为它里面基本上都是隐式bug,很难用肉眼看代码看出来。

Similar Posts:

  • C/C++内存的理解

    内存的理解(从别人那儿COPY来的) 内存可以说是C和C++语言学习的关键点. 这里说一点我的理解,一家之言,欢迎拍砖哈. 内存要想理解透彻,首先要理解内存编址.即不同的内存条,内存模块,插到机器上,具体对应的内存地址是多少. 最开始的PC机,IBM PC XT,只有640k内存.IBM是这么规划的,最低的128k,是BIOS的地址,毕竟BIOS也是汇编语言,它也需要合法地址,才能被CPU正确运行. 512k~640k,被定义为端口映射地址,即这部分地址,可能对应某一个外设上的地址,方便程序直接

  • 内存管理理解

    一 基本原理 Objective-C的内存管理机制与.Net/Java那种全自动的垃圾回收机制是不同的,它本质上还是C语言中的手动管理方式,只不过稍微加了一些自动方法. 1 Objective-C的对象生成于堆之上,生成之后,需要一个指针来指向它. ClassA *obj1 = [[ClassA alloc] init]; 2 Objective-C的对象在使用完成之后不会自动销毁,需要执行dealloc来释放空间(销毁),否则内存泄露. [obj1 dealloc]; 这带来了一个问题.下面代

  • 栈、堆内存到底是如何申请的,方法是如何入栈出栈的——内存结构理解学习

    Lee出品,转载请注明出处http://blog.csdn.net/hnulwt/article/details/42934365 对于软件开发者而言,理解和熟悉计算机内存知识是很基础的.今天我就来翻翻旧账,回顾看看有哪些点遗漏了,在此共同学习. 提起内存,我们常常想到三个区域: 1,静态区,静态变量 static variables / constant ,常量,静态变量就存储在静态区域,这个区域比较简单,只需要知道怎么通过地址访问他就行了. 2,堆 动态变量 关键字new .通过new 创建

  • mmap函数共享内存的理解

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式, 因为进程可以直接读写内存,而不需要任何 数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则 只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件.实际上,进程之间在共享内 存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域.而是保持共享区域,直 到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件.共享内存中的内容往往是在解

  • 【14】Java内存深入理解:java里的静态成员变量是放在了堆内存还是栈内存

    堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 栈区: 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问. 3.栈分为3个部分:基本类型变量区.执行环境上下文.操作指令区(存放操作指令). 方法区: 1.又叫静态区,

  • java中一个汉字和一个字母所占内存字节比较以及后台验证的减半处理

    基本概念 我们一般理解java中 一个字符char占2个字节byte 一个汉字占2个字节byte 一个字母占1个字节byte 其他情况 对于汉字来说,采用gbk编码占两字节,采用utf8编码占三个字节. String的length()方法 String s1 = "aa"; String s2 = "a好"; s1.length() s2.length() 答案都是2,因为该方法是返回字符的个数,并不是内存中的字节数. 数据库应用 java的编码不会影响数据库对汉字

  • linux 用户空间与内核空间——高端内存详解

    摘要:Linux 操作系统和驱动程序运行在内核空间, 应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针 时,对应的数据可能不在内存中.用户空间的内存映射采用段页式,而内核空间有自己的规则:本文旨在探讨内核空间的地址映射. Linux内核地址空间划分 通常32位Linux内核虚拟地址空间划分0~3G为用户空间,3~4G为内核空间(注意,内核可以使用的线性地址只有1G).注意这里是32位内核地址空间划分,6

  • 一步成高手:终极图解内存(上篇)

    DDR基本忘光了,来个扫盲贴. http://www.qqread.com/pcbase/2007/04/c306549.html ========================== <电脑高手>也都是一笔带过.作为电脑中必不可少的三大件之一(其余的两个是主板与CPU),内存是决定系统性能的关键设备之一,它就像一个临时的仓库,负责数据的中转.暂存-- 不过,虽然内存对系统性能的至关重要. 作为电脑中必不可少的三大件之一(其余的两个是主板与CPU),内存是决定系统性能的关键设备之一,它就像一个

  • 终极图解内存

    终极图解内存 这是一篇特别特别好的文章,详细,适合对内存一无所知的初学者 转贴: http://www.qqread.com/pcbase/2007/04/c306549.html 作为电脑中必不可少的三大件之一(其余的两个是主板与CPU),内存是决定系统性能的关键设备之一,它就像一个临时的仓库,负责数据的中转.暂存-- 不 过,虽然内存对系统性能的至关重要,但长期以来,DIYer并不重视内存,只是将它看作是一种买主板和CPU时顺带买的"附件",那时最多也就注意一下内 存的速度.这种现

  • (转)Java 内存区域和GC机制

    录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对JVM(Java Virtual Mach

Tags: