程序员对内存的理解(转)

在C和C++语言开发中,指针、内存一直是学习的重点。因为C语言作为一种偏底层的中低级语言,提供了大量的内存直接操作的方法,这一方面使程序的灵活度最大化,同时也为bug埋下很多隐患。

  因此,无论如何,我们都要对内存有一个清晰的理解。

  一、对内的分配

  32位操作系统支持4GB内存的连续访问,但通常把内存分为两个2GB的空间,每个进程在运行时最大可以使用2GB的私有内存(0x00000000—0x7FFFFFFF)。即理论上支持如下的大数组:

char szBuffer[2*1024*1024*1024];

  当然,由于在实际运行时,程序还有代码段、临时变量段、动态内存申请等,实际上是不可能用到上述那么大的数组的。

  至于高端的2GB内存地址(0x80000000—0xFFFFFFFF),操作系统一般内部保留使用,即供操作系统内核代码使用。在Windows和Linux平台上,一些动态链接库(Windows的dll,Linux的so)以及ocx控件等,由于是跨进程服务的,因此一般也在高2GB内存空间运行。

  可以看到,每个进程都能看到自己的2GB内存以及系统的2GB内存,但是不同进程之间是无法彼此看到对方的。当然,操作系统在底层做了很多工作,比如磁盘上的虚拟内存交换(请看下以标题),不同的内存块动态映射等等。

  二、虚拟内存

  虚拟内存的基本思想是:用廉价但缓慢的磁盘来扩充快速却昂贵的内存。在一定时刻,程序实际需要使用的虚拟内存区段的内容就被载入物理内存中。当物理内存中的数据有一段时间未被使用,它们就可能被转移到硬盘中,节省下来的物理内存空间用于载入需要使用的其他数据。

  在进程执行过程中,操作系统负责具体细节,使每个进程都以为自己拥有整个地址空间的独家访问权。这个幻觉是通过“虚拟内存”实现的。所有进程共享机器的物理内存,当内存使用完时就用磁盘保存数据。在进程运行时,数据在磁盘和内存之间来回移动。内存管理硬件负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的真正内存中,应用程序员只看到虚拟地址,并不知道自己的进程在磁盘与内存之间来回切换。

  从潜在的可能性上说,与进程有关的所有内存都将被系统所使用,如果该进程可能不会马上运行(可能它的优先级低,也可能是它处于睡眠状态),操作系统可以暂时取回所有分配给它的物理内存资源,将该进程的所有相关信息都备份到磁盘上。

  进程只能操作位于物理内存中的页面。当进程引用一个不在物理内存中的页面时,MMU就会产生一个页错误。内存对此事做出响应,并判断该引用是否有效。如果无效,内核向进程发出一个“segmentation violation(段违规)”的信号,内核从磁盘取回该页,换入内存中,一旦页面进入内存,进程便被解锁,可以重新运行——进程本身并不知道它曾经因为页面换入事件等待了一会。

  三、内存的使用

  对于程序员,我们最重要的是能理解不同进程间私有内存空间的含义。C和C++的编译器把私有内存分为3块:基栈、浮动栈和堆。如下图:

  (1)基栈:也叫静态存储区,这是编译器在编译期间就已经固定下来必须要使用的内存,如程序的代码段、静态变量、全局变量、const常量等。

  (2)浮动栈:很多书上称为“栈”,就是程序开始运行,随着函数、对象的一段执行,函数内部变量、对象的内部成员变量开始动态占用内存,浮动栈一般都有生命周期,函数结束或者对象析构,其对应的浮动栈空间的就拆除了,这部分内容总是变来变去,内存占用也不是固定,因此叫浮动栈。

  (3)堆:C和C++语言都支持动态内存申请,即程序运行期可以自由申请内存,这部分内存就是在堆空间申请的。堆位于2GB的最顶端,自上向下分配,这是避免和浮动栈混到一起,不好管理。我们用到malloc和new都是从堆空间申请的内存,new比malloc多了对象的支持,可以自动调用构造函数。另外,new创建对象,其成员变量位于堆里面。

  我们来看一个例子:

const int n = 100;
void Func(void)
{
    char ch = 0;
    char* pBuff = (char*)malloc(10);
    //…
}

  这个函数如果运行,其中n由于是全局静态变量,位于基栈,ch和pBuff这两个函数内部变量,ch位于浮动栈,而pBuff指向的由malloc分配的内存区,则位于堆栈。

  在内存理解上,最著名的例子就是线程启动时的参数传递。

  函数启动一个线程,很多时候需要向线程传参数,但是线程是异步启动的,即很可能启动函数已经退出了,而线程函数都还没有正式开始运行,因此,绝不能用启动函数的内部变量给线程传参。道理很简单,

函数的内部变量在浮动栈,但函数退出时,浮动栈自动拆除,内存空间已经被释放了。当线程启动时,按照给的参数指针去查询变量,实际上是在读一块无效的内存区域,程序会因此而崩溃。

  那怎么办呢?我们应该直接用malloc函数给需要传递的参数分配一块内存区域,将指针传入线程,线程收到后使用,最后线程退出时,free释放。

  我们来看例子:

//这个结构体就是参数表
typedef struct _CListen_ListenAcceptTask_Param_
{
    Linux_Win_SOCKET m_nSocket;
    //其他参量… …
}SCListenAcceptTaskParam;
//习惯性写法,设置结构体后,立即声明结构体的尺寸,为后续malloc提供方便
const ULONG SCListenAcceptTaskParamSize = sizeof(SCListenAcceptTaskParam);
//这里接收到连接请求,申请参数区域,将关键信息带入参数区域,帮助后续线程工作。
bool CListen::ListenTaskCallback(void* pCallParam,int& nStatus)
{
    //正常的函数逻辑… …
    //假定s是accept到的socket,需要传入后续线程工作
    //在此准备一块参数区域,从远堆上申请
    SCListenAcceptTaskParam* pParam = (SCListenAcceptTaskParam*) malloc(SCListenAcceptTaskParamSize);
    //给参数区域赋值
    pParam->m_nSocket = s;
    //此处启动线程,将pParam传递给线程… …
    //正常的函数逻辑… …
}
//这是线程函数,负责处理上文accept到的socket
bool CListen::ListenAcceptTask(void* pCallParam,int& nStatus)
{
    //第一句话就是强制指针类型转换,获得外界传入的参数区域
    SCListenAcceptTaskParam* pParam= (SCListenAcceptTaskParam*)pCallParam;
    //正常的函数逻辑… …
    //退出前,必须要做的工作,确保资源不被泄露
    close(pParam->m_nSocket); //关闭socket
    free(pCallParam); // free传入的参数区域
    //… … 

}

  四、内存bug

  无规则的滥用内存和指针会导致大量的bug,程序员应该对内存的使用保持高度的敏感性和警惕性,谨慎地使用内存资源。

  使用内存时最容易出现的bug是:

  (1)坏指针值错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针,第三种可能导致坏指针的原因是对指针进行释放之后再访问它的内容。可以修改free语句,在指针释放之后再将它置为空值。

free(p); p = NULL;

  这样,如果在指针释放之后继续使用该指针,至少程序能在终止之前进行信息转储。

  (2)改写(overwrite)错误:越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构(在动态分配内存之前的区域写入数据就很容易发生这种情况)

p = malloc(256); p[-1] = 0; p[256] = 0;

  (3)指针释放引起的错误:释放同一个内存块两次,或释放一块未曾使用malloc分配的内存,或释放仍在使用中的内存,或释放一个无效的指针。一个极为常见的与释放内存有关的错误就是在 for(p=start;p=p->next) 这样的循环中迭代一个链表,并在循环体内使用 free(p) 语句。这样,在下一次循环迭代时,程序就会对已经释放的指针进行解除引用操作,从而导致不可预料的结果。

  我们可以这样迭代:

struct node *p, *tart, *temp;
for(p = start; p ; p = temp)
{
    temp = p->next;
    free(p);
}

  总结:这些知识都是本人最近看书总结出来的,可能有很多是个人主观,欢迎拍砖…

http://kb.cnblogs.com/page/143965/

时间: 2024-08-22 14:06:56

程序员对内存的理解(转)的相关文章

java-为什么编写程序的时候数据不统一用10进制,这样程序员也比较好理解啊

问题描述 为什么编写程序的时候数据不统一用10进制,这样程序员也比较好理解啊 为什么编写程序的时候数据不统一用10进制,这样程序员也比较好理解啊 解决方案 大多数时候都是用10进制的,目的就是便于理解.但是有时候用16进制,因为在某些场合下,16进制更好理解.比如说字节数据,10进制下,529你能看出是哪两个字节么?但是0x0211,一看就是02 11两个字节. 解决方案二: 你可以用十进制,但是很多时候16进制更方便,比如控制灯的开关,十六进制加上位操作可以较为直观的编写出控制程序 解决方案三

程序员成长规划

引言 我的程序员成长之路 程序员的成长经历往往很相似,大部分的人走过了最前面相同的一段路,而有的人则走得更远.总结自己这些年来的历程,这也许能让年轻的程序员少走一些弯路,成长得更快:或许更好一些,能让大家从中得到一些启发,早日进入优秀程序员的阶段,实现梦想,释放激情. 第一阶段,最初是在学校里学习计算机基础知识,学习经典的程序设计语言,编写测试用的小程序.这个过程可以说是对计算机和程序设计的入门阶段.这个阶段主要是培养了自己对计算机软件的兴趣,打下了良好的计算机基础知识. 第二阶段,而后参加工作

为JavaScript程序员准备的10本免费书籍

你对你从事的职业中的各种概念掌握得越多,那你就越有优势.当你从事的是技术或者IT方面的工作时,你了解的信息越多,你的基本技 能和意识将越宽广.JavaScript就是这样一门编程语言,你需要掌握和学习的是永无止境的.它会时不时的增加或取消一些脚本,以提高网站建设质量. 一个利用JavaScript来设计网页.游戏或者其他图形的程序员应该知道并理解该编程语言所有重要方面的概念. JavaScript正在主宰这个世界,从创建一个简单的web应用到复杂的机器人,你都可以使用它.因此,很多作者和程序员把

在程序员的眼里,用户是这样使用他们开发软件的

简评:在编程这个行业中,有一个原则名为"KISS",当然你别想歪了,不是那个男女之间的Kiss,而是"keep it simple,stupid"的缩略用语,意为"长话短说,傻瓜",其实就是程序员在实现功能或者写一个应用时需要将用户当成"傻子",没错就是傻子...,这样写出来的程序才能不是程序猿的用户使用(哭笑ing). 我曾经说过,程序员不是一般人,是具有某种超能力的人.但问题是,程序员往往意识不到自己的这种特异功能,在他们

《程序员度量:改善软件团队的分析学》一数据选择

数据选择 为度量寻找合适的数据,有点像科学,有点像艺术,但更多的是试错.当决定使用哪些数据时,我们会面对很多选择.显然,你可以提出多种多样的测度,能获得相同的结果,或者发生几乎等同的一件事.例如,要决定一个程序员的质量测试有多好,我们可以选择去测量编写的测试用例数.代码的测试覆盖率,或者发现的bug数量和严重性.我们也可以测量所有这些.一般来说,当我不得不在多个可能使用的测度中去选择时,我基于以下经验法则来决定最优方案:选择最容易获得的数据.选择最容易让非程序员解释和理解的数据.第一条经验法则或

做程序员怎么样才能发财?

问题描述 做程序员怎么样才能发财?我想早点退休,环游世界,有花不玩的钱,做程序员有可能吗? 解决方案 黑掉网上银行解决方案二:楼上的,你在说什么?呵呵,如果你到了这个程度,就不会只想着玩了:只想着玩才不会打到这个程度解决方案三:程序员(英文Programmer)是从事程序开发.维护的专业人员.一般我们将程序员分为程序设计人员和程序编码员,但两者的界限并不非常清楚,特别是在中国.作一个真正合格的程序员,应该具有的素质.1:团队精神和协作能力团队精神和协作能力是作为一个程序员应具备的最基本的素质.软

需不需要留给程序员了解需求的时间??

问题描述 需不需要留给程序员了解需求的时间??现在我们公司是边开发边了解需求,作为一个程序员感觉很无奈... 解决方案 解决方案二:一般公司只需要你回答:能不能?是不是?.........楼主太天真了.解决方案三:都把程序员当神看了啥都不了解就开始干边干边了解,总花费了解时间没少,而且随着了解的加深,对已完成代码进行修改也很费时间.解决方案四:哪有那么多时间给你去熟悉需求,解决方案五:肯定还是先了解大局了才去动手干啊,后期再来修改代码好玩吗?跟你老板说,我是来上班的,不是来被玩的.解决方案六:打

在程序员的眼里,用户是这样使用他们开发的软件的

我曾经说过,程序员不是一般的人,是具有某种超能里的人.但问题是,程序员往往意识不到自己 的这种特异功能,在他们的眼里,会认为自己很普通,跟常人一样,所以,程序员能做到的事情,其他人--比如他们的客户/软件用户--也应该很容易做到.但 事实上,由于大部分人--绝大部分人(包括软件开发公司的客户/购买软件的用户)--都是电脑小白(对电脑知识/计算机知识/软件知识知之甚少的人).一 个对于程序员来说很显而易见的软件操作,换成让用户来操作,就会出现各种各样奇怪的事情.这让程序员非常痛苦. 记得有一次,一

产品经理怎么和程序员打交道

经常有人问我:"嗨,费杰你好!请问阿里巴巴的需求文档怎么写?",但很少听到人谈:"嗨,哥们你好!请问产品经理怎么和程序员打交道?"你回去稍微琢磨了一下,心里就会得到下面三种情况: 1)很多产品经理之前就是程序员,所以非常了解程序员是什么样的一个状态: 2)很多产品经理还停留在关注自己产品规划.设计本身,缺乏对团队配合的思考: 3)很多产品经理,其实是有和程序员打交道的实践技巧.心得的,但没有重视和分享. 事实上在很多产品研发体系下,产品经理和程序员因为思考方式.关注