﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-ison</title><link>http://www.cnitblog.com/ison/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 29 Apr 2026 17:02:11 GMT</lastBuildDate><pubDate>Wed, 29 Apr 2026 17:02:11 GMT</pubDate><ttl>60</ttl><item><title>如何在linux下检测内存泄漏</title><link>http://www.cnitblog.com/ison/archive/2009/03/02/54970.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Mon, 02 Mar 2009 02:25:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2009/03/02/54970.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/54970.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2009/03/02/54970.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/54970.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/54970.html</trackback:ping><description><![CDATA[&nbsp;
<p align=left><span>本文针对<span> linux </span>下的<span> C++ </span>程序的内存泄漏的检测方法及其实现进行探讨。其中包括<span> C++ </span>中的<span> new </span>和<span> delete </span>的基本原理，内存检测子系统的实现原理和具体方法，以及内存泄漏检测的高级话题。作为内存检测子系统实现的一部分，提供了一个具有更好的使用特性的互斥体（<span>Mutex</span>）类。</span></p>
<p align=left><a name=N10037><span>1</span></a><span><span>．开发背景</span></span></p>
<p align=left><span>在<span> windows </span>下使用<span> VC </span>编程时，我们通常需要<span> DEBUG </span>模式下运行程序，而后调试器将在退出程序时，打印出程序运行过程中在堆上分配而没有释放的内存信息，其中包括代码文件名、行号以及内存大小。该功能是<span> MFC Framework </span>提供的内置机制，封装在其类结构体系内部。</span></p>
<p align=left><span>在<span> linux </span>或者<span> unix </span>下，我们的<span> C++ </span>程序缺乏相应的手段来检测内存信息，而只能使用<span> top </span>指令观察进程的动态内存总额。而且程序退出时，我们无法获知任何内存泄漏信息。为了更好的辅助在<span> linux </span>下程序开发，我们在我们的类库项目中设计并实现了一个内存检测子系统。下文将简述<span> C++ </span>中的<span> new </span>和<span> delete </span>的基本原理，并讲述了内存检测子系统的实现原理、实现中的技巧，并对内存泄漏检测的高级话题进行了讨论。</span></p>
<p align=left>&nbsp;</p>
<p align=left><a name=N10043><span>2</span></a><span><span>．<span>New</span>和<span>delete</span>的原理</span></span></p>
<p align=left><span>当我们在程序中写下<span> new </span>和<span> delete </span>时，我们实际上调用的是<span> C++ </span>语言内置的<span> new operator </span>和<span> delete operator</span>。所谓语言内置就是说我们不能更改其含义，它的功能总是一致的。以<span> new operator </span>为例，它总是先分配足够的内存，而后再调用相应的类型的构造函数初始化该内存。而<span> delete operator </span>总是先调用该类型的析构函数，而后释放内存（图<span>1</span>）。我们能够施加影响力的事实上就是<span> new operator </span>和<span> delete operator </span>执行过程中分配和释放内存的方法。</span></p>
<p align=left></p>
<p align=left><span>new operator </span><span>为分配内存所调用的函数名字是<span> operator new</span>，其通常的形式是<span> void * operator new(size_t size); </span>其返回值类型是<span> void*</span>，因为这个函数返回一个未经处理（<span>raw</span>）的指针，未初始化的内存。参数<span> size </span>确定分配多少内存，你能增加额外的参数重载函数<span> operator new</span>，但是第一个参数类型必须是<span> size_t</span>。</span></p>
<p align=left><span>delete operator </span><span>为释放内存所调用的函数名字是<span> operator delete</span>，其通常的形式是<span> void operator delete(void *memoryToBeDeallocated)</span>；它释放传入的参数所指向的一片内存区。</span></p>
<p align=left><span>这里有一个问题，就是当我们调用<span> new operator </span>分配内存时，有一个<span> size </span>参数表明需要分配多大的内存。但是当调用<span> delete operator </span>时，却没有类似的参数，那么<span> delete operator </span>如何能够知道需要释放该指针指向的内存块的大小呢？答案是：对于系统自有的数据类型，语言本身就能区分内存块的大小，而对于自定义数据类型（如我们自定义的类），则<span> operator new </span>和<span> operator delete </span>之间需要互相传递信息。</span></p>
<p align=left><span>当我们使用<span> operator new </span>为一个自定义类型对象分配内存时，实际上我们得到的内存要比实际对象的内存大一些，这些内存除了要存储对象数据外，还需要记录这片内存的大小，此方法称为<span> cookie</span>。这一点上的实现依据不同的编译器不同。（例如<span> MFC </span>选择在所分配内存的头部存储对象实际数据，而后面的部分存储边界标志和内存大小信息。<span>g++ </span>则采用在所分配内存的头<span> 4 </span>个自己存储相关信息，而后面的内存存储对象实际数据。）当我们使用<span> delete operator </span>进行内存释放操作时，<span>delete operator </span>就可以根据这些信息正确的释放指针所指向的内存块。</span></p>
<p align=left><span>以上论述的是对于单个对象的内存分配<span>/</span>释放，当我们为数组分配<span>/</span>释放内存时，虽然我们仍然使用<span> new operator </span>和<span> delete operator</span>，但是其内部行为却有不同：<span>new operator </span>调用了<span>operator new </span>的数组版的兄弟－<span> operator new[]</span>，而后针对每一个数组成员调用构造函数。而<span> delete operator </span>先对每一个数组成员调用析构函数，而后调用<span> operator delete[] </span>来释放内存。需要注意的是，当我们创建或释放由自定义数据类型所构成的数组时，编译器为了能够标识出在<span> operator delete[] </span>中所需释放的内存块的大小，也使用了编译器相关的<span> cookie </span>技术。</span></p>
<p align=left><span>综上所述，如果我们想检测内存泄漏，就必须对程序中的内存分配和释放情况进行记录和分析，也就是说我们需要重载<span> operator new/operator new[];operator delete/operator delete[] </span>四个全局函数，以截获我们所需检验的内存操作信息。</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left><a name=N1006A><span>3</span></a><span><span>．内存检测的基本实现原理</span></span></p>
<p align=left><span>上文提到要想检测内存泄漏，就必须对程序中的内存分配和释放情况进行记录，所能够采取的办法就是重载所有形式的<span>operator new </span>和<span> operator delete</span>，截获<span> new operator </span>和<span> delete operator </span>执行过程中的内存操作信息。下面列出的就是重载形式</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>void* operator new( size_t nSize, char* pszFileName, int nLineNum )</span></p>
            <p align=left><span>void* operator new[]( size_t nSize, char* pszFileName, int nLineNum )</span></p>
            <p align=left><span>void operator delete( void *ptr )</span></p>
            <p align=left><span>void operator delete[]( void *ptr )</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>我们为<span> operator new </span>定义了一个新的版本，除了必须的<span> size_t nSize </span>参数外，还增加了文件名和行号，这里的文件名和行号就是这次<span> new operator </span>操作符被调用时所在的文件名和行号，这个信息将在发现内存泄漏时输出，以帮助用户定位泄漏具体位置。对于<span> operator delete</span>，因为无法为之定义新的版本，我们直接覆盖了全局的<span> operator delete </span>的两个版本。</span></p>
<p align=left><span>在重载的<span> operator new </span>函数版本中，我们将调用全局的<span> operator new </span>的相应的版本并将相应的 <span>size_t </span>参数传入，而后，我们将全局<span> operator new </span>返回的指针值以及该次分配所在的文件名和行号信息记录下来，这里所采用的数据结构是一个<span> STL </span>的<span> map</span>，以指针值为<span> key </span>值。当<span> operator delete </span>被调用时，如果调用方式正确的话（调用方式不正确的情况将在后面详细描述），我们就能以传入的指针值在<span> map </span>中找到相应的数据项并将之删除，而后调用<span> free </span>将指针所指向的内存块释放。当程序退出的时候，<span>map </span>中的剩余的数据项就是我们企图检测的内存泄漏信息－－已经在堆上分配但是尚未释放的分配信息。</span></p>
<p align=left><span>以上就是内存检测实现的基本原理，现在还有两个基本问题没有解决：</span></p>
<p align=left><span>1) </span><span>如何取得内存分配代码所在的文件名和行号，并让<span> new operator </span>将之传递给我们重载的<span> operator new</span>。</span></p>
<p align=left><span>2) </span><span>我们何时创建用于存储内存数据的<span> map </span>数据结构，如何管理，何时打印内存泄漏信息。</span></p>
<p align=left><span>先解决问题<span>1</span>。首先我们可以利用<span> C </span>的预编译宏<span> __FILE__ </span>和<span> __LINE__</span>，这两个宏将在编译时在指定位置展开为该文件的文件名和该行的行号。而后我们需要将缺省的全局<span> new operator </span>替换为我们自定义的能够传入文件名和行号的版本，我们在子系统头文件<span> MemRecord.h </span>中定义：</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>#define DEBUG_NEW new(__FILE__, __LINE__ )</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>而后在所有需要使用内存检测的客户程序的所有的<span> cpp </span>文件的开头加入</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>#include "MemRecord.h"</span></p>
            <p align=left><span>#define new DEBUG_NEW</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>就可以将客户源文件中的对于全局缺省的<span> new operator </span>的调用替换为<span> new (__FILE__,__LINE__) </span>调用，而该形式的<span>new operator</span>将调用我们的<span>operator new (size_t nSize, char* pszFileName, int nLineNum)</span>，其中<span> nSize </span>是由<span> new operator </span>计算并传入的，而<span> new </span>调用点的文件名和行号是由我们自定义版本的<span> new operator </span>传入的。我们建议在所有用户自己的源代码文件中都加入上述宏，如果有的文件中使用内存检测子系统而有的没有，则子系统将可能因无法监控整个系统而输出一些泄漏警告。</span></p>
<p align=left><span>再说第二个问题。我们用于管理客户信息的这个<span> map </span>必须在客户程序第一次调用<span> new operator </span>或者<span> delete operator </span>之前被创建，而且在最后一个<span> new operator </span>和<span> delete operator </span>调用之后进行泄漏信息的打印，也就是说它需要先于客户程序而出生，而在客户程序退出之后进行分析。能够包容客户程序生命周期的确有一人<span>--</span>全局对象（<span>appMemory</span>）。我们可以设计一个类来封装这个<span> map </span>以及这对它的插入删除操作，然后构造这个类的一个全局对象（<span>appMemory</span>），在全局对象（<span>appMemory</span>）的构造函数中创建并初始化这个数据结构，而在其析构函数中对数据结构中剩余数据进行分析和输出。<span>Operator new </span>中将调用这个全局对象（<span>appMemory</span>）的<span> insert </span>接口将指针、文件名、行号、内存块大小等信息以指针值为<span> key </span>记录到<span> map </span>中，在<span> operator delete </span>中调用<span> erase </span>接口将对应指针值的<span> map </span>中的数据项删除，注意不要忘了对<span> map </span>的访问需要进行互斥同步，因为同一时间可能会有多个线程进行堆上的内存操作。</span></p>
<p align=left><span>好啦，内存检测的基本功能已经具备了。但是不要忘了，我们为了检测内存泄漏，在全局的<span> operator new </span>增加了一层间接性，同时为了保证对数据结构的安全访问增加了互斥，这些都会降低程序运行的效率。因此我们需要让用户能够方便的<span> enable </span>和<span> disable </span>这个内存检测功能，毕竟内存泄漏的检测应该在程序的调试和测试阶段完成。我们可以使用条件编译的特性，在用户被检测文件中使用如下宏定义：</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>#include "MemRecord.h"</span></p>
            <p align=left><span>#if defined( MEM_DEBUG )</span></p>
            <p align=left><span>#define new DEBUG_NEW</span></p>
            <p align=left><span>#endif</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>当用户需要使用内存检测时，可以使用如下命令对被检测文件进行编译</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>g++ -c -DMEM_DEBUG xxxxxx.cpp</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>就可以<span> enable </span>内存检测功能，而用户程序正式发布时，可以去掉<span> -DMEM_DEBUG </span>编译开关来<span> disable </span>内存检测功能，消除内存检测带来的效率影响。</span></p>
<p align=left><span>图<span>2</span>所示为使用内存检测功能后，内存泄漏代码的执行以及检测结果</span></p>
<p align=left><span><br></span><span>图<span>2 </span></span></p>
<table cellSpacing=0 cellPadding=0 align=right border=0>
    <tbody>
        <tr>
            <td>
            <p align=left>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left><a name=N100BA><span>4</span></a><span><span>．错误方式删除带来的问题</span></span></p>
<p align=left><span>以上我们已经构建了一个具备基本内存泄漏检测功能的子系统，下面让我们来看一下关于内存泄漏方面的一些稍微高级一点的话题。</span></p>
<p align=left><span>首先，在我们编制<span> c++ </span>应用时，有时需要在堆上创建单个对象，有时则需要创建对象的数组。关于<span> new </span>和<span> delete </span>原理的叙述我们可以知道，对于单个对象和对象数组来说，内存分配和删除的动作是大不相同的，我们应该总是正确的使用彼此搭配的<span> new </span>和<span> delete </span>形式。但是在某些情况下，我们很容易犯错误，比如如下代码：</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>class Test {};</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Test* pAry = new Test[10];//</span><span>创建了一个拥有<span> 10 </span>个<span> Test </span>对象的数组</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Test* pObj = new Test;//</span><span>创建了一个单对象</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete []pObj;//</span><span>本应使用单对象形式<span> delete pObj </span>进行内存释放，却错误的使用了数</span></p>
            <p align=left><span>//</span><span>组形式</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete pAry;//</span><span>本应使用数组形式<span> delete []pAry </span>进行内存释放，却错误的使用了单对</span></p>
            <p align=left><span>//</span><span>象的形式</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>不匹配的<span> new </span>和<span> delete </span>会导致什么问题呢？<span>C++ </span>标准对此的解答是<span>"</span>未定义<span>"</span>，就是说没有人向你保证会发生什么，但是有一点可以肯定：大多不是好事情<span>--</span>在某些编译器形成的代码中，程序可能会崩溃，而另外一些编译器形成的代码中，程序运行可能毫无问题，但是可能导致内存泄漏。</span></p>
<p align=left><span>既然知道形式不匹配的<span> new </span>和<span> delete </span>会带来的问题，我们就需要对这种现象进行毫不留情的揭露，毕竟我们重载了所有形式的内存操作<span> operator new</span>，<span>operator new[]</span>，<span>operator delete</span>，<span>operator delete[]</span>。</span></p>
<p align=left><span>我们首先想到的是，当用户调用特定方式（单对象或者数组方式）的<span> operator new </span>来分配内存时，我们可以在指向该内存的指针相关的数据结构中，增加一项用于描述其分配方式。当用户调用不同形式的<span> operator delete </span>的时候，我们在<span> map </span>中找到与该指针相对应的数据结构，然后比较分配方式和释放方式是否匹配，匹配则在<span> map </span>中正常删除该数据结构，不匹配则将该数据结构转移到一个所谓<span> "ErrorDelete" </span>的<span> list </span>中，在程序最终退出的时候和内存泄漏信息一起打印。</span></p>
<p align=left><span>上面这种方法是最顺理成章的，但是在实际应用中效果却不好。原因有两个，第一个原因我们上面已经提到了：当<span> new </span>和<span> delete </span>形式不匹配时，其结果<span>"</span>未定义<span>"</span>。如果我们运气实在太差<span>--</span>程序在执行不匹配的<span> delete </span>时崩溃了，我们的全局对象（<span>appMemory</span>）中存储的数据也将不复存在，不会打印出任何信息。第二个原因与编译器相关，前面提到过，当编译器处理自定义数据类型或者自定义数据类型数组的<span> new </span>和<span> delete </span>操作符的时候，通常使用编译器相关的<span> cookie </span>技术。这种<span> cookie </span>技术在编译器中可能的实现方式是：<span>new operator </span>先计算容纳所有对象所需的内存大小，而后再加上它为记录<span> cookie </span>所需要的内存量，再将总容量传给<span>operator new </span>进行内存分配。当<span> operator new </span>返回所需的内存块后，<span>new operator </span>将在调用相应次数的构造函数初始化有效数据的同时，记录<span> cookie </span>信息。而后将指向有效数据的指针返回给用户。也就是说我们重载的<span> operator new </span>所申请到并记录下来的指针与<span> new operator </span>返回给调用者的指针不一定一致（图<span>3</span>）。当调用者将<span> new operator </span>返回的指针传给<span> delete operator </span>进行内存释放时，如果其调用形式相匹配，则相应形式的<span> delete operator </span>会作出相反的处理，即调用相应次数的析构函数，再通过指向有效数据的指针位置找出包含<span> cookie </span>的整块内存地址，并将其传给<span> operator delete </span>释放内存。如果调用形式不匹配，<span>delete operator </span>就不会做上述运算，而直接将指向有效数据的指针（而不是真正指向整块内存的指针）传入<span> operator delete</span>。因为我们在<span> operator new </span>中记录的是我们所分配的整块内存的指针，而现在传入<span> operator delete </span>的却不是，所以就无法在全局对象（<span>appMemory</span>）所记录的数据中找到相应的内存分配信息。</span></p>
<p align=left><span><br></span><span>图<span>3 </span></span></p>
<p align=left><span>综上所述，当<span> new </span>和<span> delete </span>的调用形式不匹配时，由于程序有可能崩溃或者内存子系统找不到相应的内存分配信息，在程序最终打印出<span> "ErrorDelete" </span>的方式只能检测到某些<span>"</span>幸运<span>"</span>的不匹配现象。但我们总得做点儿什么，不能让这种危害极大的错误从我们眼前溜走，既然不能秋后算帐，我们就实时输出一个<span> warning </span>信息来提醒用户。什么时候抛出一个<span> warning </span>呢？很简单，当我们发现在<span> operator delete </span>或<span> operator delete[] </span>被调用的时候，我们无法在全局对象（<span>appMemory</span>）的<span> map </span>中找到与传入的指针值相对应的内存分配信息，我们就认为应该提醒用户。</span></p>
<p align=left><span>既然决定要输出<span>warning</span>信息，那么现在的问题就是：我们如何描述我们的<span>warning</span>信息才能更便于用户定位到不匹配删除错误呢？答案：在<span> warning </span>信息中打印本次<span> delete </span>调用的文件名和行号信息。这可有点困难了，因为对于<span> operator delete </span>我们不能向对象<span> operator new </span>一样做出一个带附加信息的重载版本，我们只能在保持其接口原貌的情况下，重新定义其实现，所以我们的<span> operator delete </span>中能够得到的输入只有指针值。在<span> new/delete </span>调用形式不匹配的情况下，我们很有可能无法在全局对象（<span>appMemory</span>）的<span> map </span>中找到原来的<span> new </span>调用的分配信息。怎么办呢？万不得已，只好使用全局变量了。我们在检测子系统的实现文件中定义了两个全局变量（<span>DELETE_FILE,DELETE_LINE</span>）记录<span> operator delete </span>被调用时的文件名和行号，同时为了保证并发的<span> delete </span>操作对这两个变量访问同步，还使用了一个<span> mutex</span>（至于为什么是<span> CCommonMutex </span>而不是一个<span> pthread_mutex_t</span>，在<span>"</span>实现上的问题<span>"</span>一节会详细论述，在这里它的作用就是一个<span> mutex</span>）。</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>char DELETE_FILE[ FILENAME_LENGTH ] = {0};</span></p>
            <p align=left><span>int DELETE_LINE = 0;</span></p>
            <p align=left><span>CCommonMutex globalLock;</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>而后，在我们的检测子系统的头文件中定义了如下形式的<span> DEBUG_DELETE</span></span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>extern char DELETE_FILE[ FILENAME_LENGTH ];</span></p>
            <p align=left><span>extern int DELETE_LINE;</span></p>
            <p align=left><span>extern CCommonMutex globalLock;//</span><span>在后面解释</span></p>
            <p align=left><span>#define DEBUG_DELETE<span>&nbsp;&nbsp;&nbsp;&nbsp; </span>globalLock.Lock(); \</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if (DELETE_LINE != 0) BuildStack();\ </span><span>（<span>//</span>见第六节解释）</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>strncpy( DELETE_FILE, __FILE__,FILENAME_LENGTH - 1 );\</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DELETE_FILE[ FILENAME_LENGTH - 1 ]= '\0'; \</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>DELETE_LINE = __LINE__; \</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>在用户被检测文件中原来的宏定义中添加一条：</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>#include "MemRecord.h"</span></p>
            <p align=left><span>#if defined( MEM_DEBUG )</span></p>
            <p align=left><span>#define new DEBUG_NEW</span></p>
            <p align=left><span>#define delete DEBUG_DELETE</span></p>
            <p align=left><span>#endif</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>这样，在用户被检测文件调用<span> delete operator </span>之前，将先获得互斥锁，然后使用调用点文件名和行号对相应的全局变量（<span>DELETE_FILE,DELETE_LINE</span>）进行赋值，而后调用<span> delete operator</span>。当<span> delete operator </span>最终调用我们定义的<span> operator delete </span>的时候，在获得此次调用的文件名和行号信息后，对文件名和行号全局变量（<span>DELETE_FILE,DELETE_LINE</span>）重新初始化并打开互斥锁，让下一个挂在互斥锁上的<span> delete operator </span>得以执行。</span></p>
<p align=left><span>在对<span> delete operator </span>作出如上修改以后，当我们发现无法经由<span> delete operator </span>传入的指针找到对应的内存分配信息的时候，就打印包括该次调用的文件名和行号的<span> warning</span>。</span></p>
<p align=left><span>天下没有十全十美的事情，既然我们提供了一种针对错误方式删除的提醒方法，我们就需要考虑以下几种异常情况：</span></p>
<p align=left><span>1</span><span>． 用户使用的第三方库函数中有内存分配和释放操作。或者用户的被检测进程中进行内存分配和释放的实现文件没有使用我们的宏定义。由于我们替换了全局的<span> operator delete</span>，这种情况下的用户调用的<span> delete </span>也会被我们截获。用户并没有使用我们定义的<span>DEBUG_NEW </span>宏，所以我们无法在我们的全局对象（<span>appMemory</span>）数据结构中找到对应的内存分配信息，但是由于它也没有使用<span>DEBUG_DELETE</span>，我们为<span> delete </span>定义的两个全局<span> DELETE_FILE </span>和<span> DELETE_LINE </span>都不会有值，因此可以不打印<span> warning</span>。</span></p>
<p align=left><span>2</span><span>． 用户的一个实现文件调用了<span> new </span>进行内存分配工作，但是该文件并没有使用我们定义的<span> DEBUG_NEW </span>宏。同时用户的另一个实现文件中的代码负责调用<span> delete </span>来删除前者分配的内存，但不巧的是，这个文件使用了<span> DEBUG_DELETE </span>宏。这种情况下内存检测子系统会报告<span> warning</span>，并打印出<span> delete </span>调用的文件名和行号。</span></p>
<p align=left><span>3</span><span>． 与第二种情况相反，用户的一个实现文件调用了<span> new </span>进行内存分配工作，并使用我们定义的<span> DEBUG_NEW </span>宏。同时用户的另一个实现文件中的代码负责调用<span> delete </span>来删除前者分配的内存，但该文件没有使用<span> DEBUG_DELETE </span>宏。这种情况下，因为我们能够找到这个内存分配的原始信息，所以不会打印<span> warning</span>。</span></p>
<p align=left><span>4</span><span>． 当出现嵌套<span> delete</span>（定义可见<span>"</span>实现上的问题<span>"</span>）的情况下，以上第一和第三种情况都有可能打印出不正确的<span> warning </span>信息，详细分析可见<span>"</span>实现上的问题<span>"</span>一节。</span></p>
<p align=left><span>你可能觉得这样的<span> warning </span>太随意了，有误导之嫌。怎么说呢？作为一个检测子系统，对待有可能的错误我们所采取的原则是：宁可误报，不可漏报。请大家<span>"</span>有则改之，无则加勉<span>"</span>。</span></p>
<table cellSpacing=0 cellPadding=0 align=right border=0>
    <tbody>
        <tr>
            <td>
            <p align=right></p>
            <div align=right>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p align=left>&nbsp;</p>
                        </td>
                        <td vAlign=top>
                        <p align=right>&nbsp;</p>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            <p align=right></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><a name=N10112><span>5</span></a><span><span>．动态内存泄漏信息的检测</span></span></p>
<p align=left><span>上面我们所讲述的内存泄漏的检测能够在程序整个生命周期结束时，打印出在程序运行过程中已经在堆上分配但是没有释放的内存分配信息，程序员可以由此找到程序中<span>"</span>显式<span>"</span>的内存泄漏点并加以改正。但是如果程序在结束之前能够将自己所分配的所有内存都释放掉，是不是就可以说这个程序不存在内存泄漏呢？答案：否！在编程实践中，我们发现了另外两种危害性更大的<span>"</span>隐式<span>"</span>内存泄漏，其表现就是在程序退出时，没有任何内存泄漏的现象，但是在程序运行过程中，内存占用量却不断增加，直到使整个系统崩溃。</span></p>
<p align=left><span>1</span><span>． 程序的一个线程不断分配内存，并将指向内存的指针保存在一个数据存储中（如<span> list</span>），但是在程序运行过程中，一直没有任何线程进行内存释放。当程序退出的时候，该数据存储中的指针值所指向的内存块被依次释放。</span></p>
<p align=left><span>2</span><span>． 程序的<span>N</span>个线程进行内存分配，并将指针传递给一个数据存储，由<span>M</span>个线程从数据存储进行数据处理和内存释放。由于<span> N </span>远大于<span>M</span>，或者<span>M</span>个线程数据处理的时间过长，导致内存分配的速度远大于内存被释放的速度。但是在程序退出的时候，数据存储中的指针值所指向的内存块被依次释放。</span></p>
<p align=left><span>之所以说他危害性更大，是因为很不容易这种问题找出来，程序可能连续运行几个十几个小时没有问题，从而通过了不严密的系统测试。但是如果在实际环境中<span> 7&#215;24 </span>小时运行，系统将不定时的崩溃，而且崩溃的原因从<span> log </span>和程序表象上都查不出原因。</span></p>
<p align=left><span>为了将这种问题也挑落马下，我们增加了一个动态检测模块<span> MemSnapShot</span>，用于在程序运行过程中，每隔一定的时间间隔就对程序当前的内存总使用情况和内存分配情况进行统计，以使用户能够对程序的动态内存分配状况进行监视。</span></p>
<p align=left><span>当客户使用<span> MemSnapShot </span>进程监视一个运行中的进程时，被监视进程的内存子系统将把内存分配和释放的信息实时传送给<span>MemSnapShot</span>。<span>MemSnapShot </span>则每隔一定的时间间隔就对所接收到的信息进行统计，计算该进程总的内存使用量，同时以调用<span>new</span>进行内存分配的文件名和行号为索引值，计算每个内存分配动作所分配而未释放的内存总量。这样一来，如果在连续多个时间间隔的统计结果中，如果某文件的某行所分配的内存总量不断增长而始终没有到达一个平衡点甚至回落，那它一定是我们上面所说到的两种问题之一。</span></p>
<p align=left><span>在实现上，内存检测子系统的全局对象（<span>appMemory</span>）的构造函数中以自己的当前<span> PID </span>为基础<span> key </span>值创建一个消息队列，并在<span>operator new </span>和<span> operator delete </span>被调用的时候将相应的信息写入消息队列。<span>MemSnapShot </span>进程启动时需要输入被检测进程的<span> PID</span>，而后通过该<span> PID </span>组装<span> key </span>值并找到被检测进程创建的消息队列，并开始读入消息队列中的数据进行分析统计。当得到<span>operator new </span>的信息时，记录内存分配信息，当收到<span> operator delete </span>消息时，删除相应的内存分配信息。同时启动一个分析线程，每隔一定的时间间隔就计算一下当前的以分配而尚未释放的内存信息，并以内存的分配位置为关键字进行统计，查看在同一位置（相同文件名和行号）所分配的内存总量和其占进程总内存量的百分比。</span></p>
<p align=left><span>图<span>4 </span>是一个正在运行的<span> MemSnapShot </span>程序，它所监视的进程的动态内存分配情况如图所示：</span></p>
<p align=left><span><br></span><span>图四<span> </span></span></p>
<p align=left><span>在支持<span> MemSnapShot </span>过程中的实现上的唯一技巧是<span>--</span>对于被检测进程异常退出状况的处理。因为被检测进程中的内存检测子系统创建了用于进程间传输数据的消息队列，它是一个核心资源，其生命周期与内核相同，一旦创建，除非显式的进行删除或系统重启，否则将不被释放。</span></p>
<p align=left><span>不错，我们可以在内存检测子系统中的全局对象（<span>appMemory</span>）的析构函数中完成对消息队列的删除，但是如果被检测进程非正常退出（<span>CTRL+C</span>，段错误崩溃等），消息队列可就没人管了。那么我们可以不可以在全局对象（<span>appMemory</span>）的构造函数中使用<span> signal </span>系统调用注册<span> SIGINT</span>，<span>SIGSEGV </span>等系统信号处理函数，并在处理函数中删除消息队列呢？还是不行，因为被检测进程完全有可能注册自己的对应的信号处理函数，这样就会替换我们的信号处理函数。最终我们采取的方法是利用<span> fork </span>产生一个孤儿进程，并利用这个进程监视被检测进程的生存状况，如果被检测进程已经退出（无论正常退出还是异常退出），则试图删除被检测进程所创建的消息队列。下面简述其实现原理：</span></p>
<p align=left><span>在全局对象（<span>appMemory</span>）构造函数中，创建消息队列成功以后，我们调用<span> fork </span>创建一个子进程，而后该子进程再次调用<span> fork </span>创建孙子进程，并退出，从而使孙子进程变为一个<span>"</span>孤儿<span>"</span>进程（之所以使用孤儿进程是因为我们需要切断被检测进程与我们创建的进程之间的信号联系）。孙子进程利用父进程（被检测进程）的全局对象（<span>appMemory</span>）得到其<span> PID </span>和刚刚创建的消息队列的标识，并传递给调用<span> exec </span>函数产生的一个新的程序映象<span>--MemCleaner</span>。</span></p>
<p align=left><span>MemCleaner </span><span>程序仅仅调用<span> kill(pid, 0);</span>函数来查看被检测进程的生存状态，如果被检测进程不存在了（正常或者异常退出），则<span> kill </span>函数返回非<span> 0 </span>值，此时我们就动手清除可能存在的消息队列。</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left><a name=N10148><span>6</span></a><span><span>．实现上的问题：嵌套<span>delete</span></span></span></p>
<p align=left><span>在<span>"</span>错误方式删除带来的问题<span>"</span>一节中，我们对<span> delete operator </span>动了个小手术<span>--</span>增加了两个全局变量（<span>DELETE_FILE,DELETE_LINE</span>）用于记录本次<span> delete </span>操作所在的文件名和行号，并且为了同步对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）的访问，增加了一个全局的互斥锁。在一开始，我们使用的是<span> pthread_mutex_t</span>，但是在测试中，我们发现<span> pthread_mutex_t </span>在本应用环境中的局限性。</span></p>
<p align=left><span>例如如下代码：</span></p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>class B {&#8230;};</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>class A {</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>public:</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>A() {m_pB = NULL};</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>A(B* pb) {m_pB = pb;};</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>~A() </span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if (m_pB != NULL)</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>行号<span>1<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete m_pB;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span>这句最要命</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>};</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>private:</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>class B* m_pB;</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int main()</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>A* pA = new A(new B);</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>行号<span>2<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete pA;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>在上述代码中，<span>main </span>函数中的一句<span> delete pA </span>我们称之为<span>"</span>嵌套删除<span>"</span>，即我们<span> delete A </span>对象的时候，在<span>A</span>对象的析构执行了另一个<span> delete B </span>的动作。当用户使用我们的内存检测子系统时，<span>delete pA </span>的动作应转化为以下动作：</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>上全局锁</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>全局变量（<span>DELETE_FILE,DELETE_LINE</span>）赋值为文件名和行号<span>2</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete operator A</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调用<span>~A()</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>上全局锁</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>全局变量（<span>DELETE_FILE,DELETE_LINE</span>）赋值为文件名和行号<span>1</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>delete operator B</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调用<span>~B()</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>返回<span>~B()</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调用<span>operator delete B</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>记录全局变量（<span>DELETE_FILE,DELETE_LINE</span>）值<span>1</span>并清除全局变量（<span>DELETE_FILE,DELETE_LINE</span>）值</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>打开全局锁</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>返回<span>operator delete B</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>返回<span>delete operator B</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>返回<span>~A()</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调用<span> operator delete A</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>记录全局变量（<span>DELETE_FILE,DELETE_LINE</span>）值<span>1</span>并清除全局变量（<span>DELETE_FILE,DELETE_LINE</span>）值</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>打开全局锁</span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>返回<span>operator delete A</span></span></p>
            <p align=left><span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>返回<span> delete operator A</span></span></p>
            <p align=left><span><span>&nbsp;&nbsp;&nbsp; </span></span></p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left>&nbsp;</p>
<p align=left><span>在这一过程中，有两个技术问题，一个是 <strong><span>mutex </span>的可重入问题</strong>，一个是嵌套删除时 <strong>对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）</strong>现场保护的问题。<span> </span></span></p>
<p align=left><span>所谓 <strong><span>mutex </span>的可重入问题</strong>，是指在同一个线程上下文中，连续对同一个<span> mutex </span>调用了多次<span> lock</span>，然后连续调用了多次<span> unlock</span>。这就是说我们的应用方式要求互斥锁有如下特性：<span> </span></span></p>
<p align=left><span>1</span><span>． 要求在同一个线程上下文中，能够多次持有同一个互斥体。并且只有在同一线程上下文中调用相同次数的<span> unlock </span>才能放弃对互斥体的占有。</span></p>
<p align=left><span>2</span><span>． 对于不同线程上下文持有互斥体的企图，同一时间只有一个线程能够持有互斥体，并且只有在其释放互斥体之后，其他线程才能持有该互斥体。</span></p>
<p align=left><span>Pthread_mutex_t </span><span>互斥体不具有以上特性，即使在同一上下文中，第二次调用<span> pthread_mutex_lock </span>将会挂起。因此，我们必须实现出自己的互斥体。在这里我们使用<span> semaphore </span>的特性实现了一个符合上述特性描述的互斥体<span> CCommonMutex</span>（源代码见附件）。</span></p>
<p align=left><span>为了支持特性<span> 2</span>，在这个<span> CCommonMutex </span>类中，封装了一个<span> semaphore</span>，并在构造函数中令其资源值为<span> 1</span>，初始值为<span>1</span>。当调用<span> CCommonMutex::lock </span>接口时，调用<span> sem_wait </span>得到<span> semaphore</span>，使信号量的资源为<span> 0 </span>从而让其他调用<span> lock </span>接口的线程挂起。当调用接口<span> CCommonMutex::unlock </span>时，调用<span> sem_post </span>使信号量资源恢复为<span> 1</span>，让其他挂起的线程中的一个持有信号量。</span></p>
<p align=left><span>同时为了支持特性<span> 1</span>，在这个<span> CCommonMutex </span>增加了对于当前线程<span> pid </span>的判断和当前线程访问计数。当线程第一次调用<span> lock </span>接口时，我们调用<span> sem_wait </span>的同时，记录当前的<span> Pid </span>到成员变量<span> m_pid</span>，并置访问计数为<span> 1</span>，同一线程（<span>m_pid == getpid()</span>）其后的多次调用将只进行计数而不挂起。当调用<span> unlock </span>接口时，如果计数不为<span> 1</span>，则只需递减访问计数，直到递减访问计数为<span> 1 </span>才进行清除<span> pid</span>、调用<span> sem_post</span>。（具体代码可见附件）</span></p>
<p align=left><strong><span>嵌套删除时对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）</span></strong><span>现场保护的问题是指，上述步骤中在<span> A </span>的析构函数中调用<span> delete m_pB </span>时，对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）文件名和行号的赋值将覆盖主程序中调用<span> delete pA </span>时对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）的赋值，造成了在执行<span> operator delete A </span>时，<span>delete pA </span>的信息全部丢失。<span> </span></span></p>
<table cellSpacing=0 cellPadding=0 width="99%" border=0>
    <tbody>
        <tr>
            <td>
            <p align=left><span>要想对这些全局信息进行现场保护，最好用的就是堆栈了，在这里我们使用了<span> STL </span>提供的<span> stack </span>容器。在<span> DEBUG_DELETE </span>宏定义中，对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）赋值之前，我们先判断是否前面已经有人对他们赋过值了<span>--</span>观察行号变量是否等于<span> 0</span>，如果不为<span> 0</span>，则应该将已有的信息压栈（调用一个全局函数<span> BuildStack() </span>将当前的全局文件名和行号数据压入一个全局堆栈<span>globalStack</span>），而后再对全局变量（<span>DELETE_FILE,DELETE_LINE</span>）赋值，再调用<span> delete operator</span>。而在内存子系统的全局对象（<span>appMemory</span>）提供的<span> erase </span>接口里面，如果判断传入的文件名和行号为<span> 0</span>，则说明我们所需要的数据有可能被嵌套删除覆盖了，所以需要从堆栈中弹出相应的数据进行处理。现在嵌套删除中的问题基本解决了，但是当嵌套删除与<span> "</span>错误方式删除带来的问题<span>"</span>一节的最后所描述的第一和第三种异常情况同时出现的时候，由于用户的<span> delete </span>调用没有通过我们定义的<span> DEBUG_DELETE </span>宏，上述机制可能出现问题。其根本原因是我们利用<span>stack </span>保留了经由我们的<span> DEBUG_DELETE </span>宏记录的<span> delete </span>信息的现场，以便在<span> operator delete </span>和全局对象（<span>appMemory</span>）的<span> erase </span>接口中使用，但是用户的没经过<span> DEBUG_DELETE </span>宏的 <span>delete </span>操作却未曾进行压栈操作而直接调用了<span> operator delete</span>，有可能将不属于这次操作的<span> delete </span>信息弹出，破坏了堆栈信息的顺序和有效性。那么，当我们因为无法找到这次及其后续的<span> delete </span>操作所对应的内存分配信息的时候，可能会打印出错误的<span> warning </span>信息。</span></p>
            </td>
        </tr>
    </tbody>
</table>
<table cellSpacing=0 cellPadding=0 align=right border=0>
    <tbody>
        <tr>
            <td>
            <p align=right>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<p align=left><a name=N10189><span>展望</span></a></p>
<p align=left><span>以上就是我们所实现的内存泄漏检测子系统的原理和技术方案，第一版的源代码在附件中，已经经过了较严格的系统测试。但是限于我们的<span> C++ </span>知识水平和编程功底，在实现过程中肯定还有没有注意到的地方甚至是缺陷，希望能够得到大家的指正，我的<span> email </span>是 <span><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#99;&#111;&#100;&#101;&#64;&#50;&#49;&#99;&#110;&#46;&#99;&#111;&#109;"><span>hcode@21cn.com</span></a></span>。<span> </span></span></p>
<p align=left><span>在我们所实现的内存检测子系统基础上，可以继续搭建内存分配优化子系统，从而形成一个完整的内存子系统。一种内存分配优化子系统的实现方案是一次性分配大块的内存，并使用特定的数据结构管理之，当内存分配请求到来时，使用特定算法从这块大内存中划定所需的一块给用户使用，而用户使用完毕，在将其划为空闲内存。这种内存优化方式将内存分配释放转换为简单的数据处理，极大的减少了内存申请和释放所耗费的时间。</span></p>
<p>&nbsp;</p>
<img src ="http://www.cnitblog.com/ison/aggbug/54970.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2009-03-02 10:25 <a href="http://www.cnitblog.com/ison/archive/2009/03/02/54970.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字编程</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52262.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 09:00:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52262.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52262.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52262.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52262.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52262.html</trackback:ping><description><![CDATA[<strong>一、客户机/服务器模式<wbr><br></strong>在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model)。该模式的建立基于以下两点：1、非对等作用；2、通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式：<br><br>首先服务器方要先启动，并根据请示提供相应服务：（过程如下）<br>1、打开一通信通道并告知本地主机，它愿意在某一个公认地址上接收客户请求。<br>2、等待客户请求到达该端口。<br>3、接收到重复服务请求，处理该请求并发送应答信号。<br>4、返回第二步，等待另一客户请求<br>5、关闭服务器。<br>客户方：<br>1、打开一通信通道，并连接到服务器所在主机的特定端口。<br>2、向服务器发送服务请求报文，等待并接收应答；继续提出请求&#8230;&#8230;<br>3、请求结束后关闭通信通道并终止。<br><br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=16 src="http://www.vckbase.net/document/image/paragraph.gif" width=14 border=0><wbr><strong><wbr> 二、基本套接字</strong><wbr><br>为了更好说明套接字编程原理，给出几个基本的套接字，在以后的篇幅中会给出更详细的使用说明。<br>1、创建套接字——socket()<br>功能：使用前创建一个新的套接字<br>格式：SOCKET PASCAL FAR socket(int af,int type,int procotol);<br>参数：af: 通信发生的区域<br>type: 要建立的套接字类型<br>procotol: 使用的特定协议<br><br>2、指定本地地址——bind()<br>功能：将套接字地址与所创建的套接字号联系起来。<br>格式：int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);<br>参数：s: 是由socket()调用返回的并且未作连接的套接字描述符（套接字号）。<br>其它：没有错误，bind()返回0，否则SOCKET_ERROR<br>地址结构说明：<br>struct sockaddr_in<br>{<br>short sin_family;//AF_INET<br>u_short sin_port;//16位端口号，网络字节顺序<br>struct in_addr sin_addr;//32位IP地址，网络字节顺序<br>char sin_zero[8];//保留<br>}<br><br>3、建立套接字连接——connect()和accept()<br>功能：共同完成连接工作<br>格式：int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);<br>SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen);<br>参数：同上<br><br>4、监听连接——listen()<br>功能：用于面向连接服务器，表明它愿意接收连接。<br>格式：int PASCAL FAR listen(SOCKET s, int backlog);<br><br>5、数据传输——send()与recv()<br>功能：数据的发送与接收<br>格式：int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);<br>int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);<br>参数：buf:指向存有传输数据的缓冲区的指针。 6、多路复用——select()<br>功能：用来检测一个或多个套接字状态。<br>格式：int PASCAL FAR select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds, <br>fd_set FAR * exceptfds,const struct timeval FAR * timeout);<br>参数：readfds:指向要做读检测的指针<br>writefds:指向要做写检测的指针<br>exceptfds:指向要检测是否出错的指针<br>timeout:最大等待时间<br>7、关闭套接字——closesocket()<br>功能：关闭套接字s<br>格式：BOOL PASCAL FAR closesocket(SOCKET s);<br><br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=16 src="http://www.vckbase.net/document/image/paragraph.gif" width=14 border=0><wbr> <strong><wbr>三、典型过程图</strong><wbr><br>2.1 面向连接的套接字的系统调用时序图<br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=575 src="http://www.vckbase.net/document/journal/vckbase16/images/socket2.1.gif" width=448 border=0><wbr> <br><br><br>2.2 无连接协议的套接字调用时序图<br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=287 src="http://www.vckbase.net/document/journal/vckbase16/images/socket2.2.gif" width=423 border=0><wbr> <br><br><br>2.3 面向连接的应用程序流程图<br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=349 src="http://www.vckbase.net/document/journal/vckbase16/images/socket2.3.gif" width=423 border=0><wbr> <img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52262.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 17:00 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52262.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP/IP</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52261.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:59:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52261.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52261.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52261.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52261.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52261.html</trackback:ping><description><![CDATA[<strong>一、TCP/IP 体系结构与特点<br><br>1、TCP/IP体系结构<wbr><br><br></strong>TCP/IP协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输层服务，而IP则是提供网络层服务。TCP/IP包括以下协议：（结构如图1.1）<br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=388 src="http://www.vckbase.com/document/journal/vckbase16/images/winsock1_1.gif" width=468 border=0><wbr><br>(图1.1) <br><br>IP： 网间协议(Internet Protocol) 负责主机间数据的路由和网络上数据的存储。同时为ICMP，TCP，UDP提供分组发送服务。用户进程通常不需要涉及这一层。<br>ARP： 地址解析协议(Address Resolution Protocol)<br>此协议将网络地址映射到硬件地址。<br>RARP： 反向地址解析协议(Reverse Address Resolution Protocol)<br>此协议将硬件地址映射到网络地址<br>ICMP： 网间报文控制协议(Internet Control Message Protocol)<br>此协议处理信关和主机的差错和传送控制。<br>TCP： 传送控制协议(Transmission Control Protocol)<br>这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务，并为数据可靠传输建立检查。（注：大多数网络用户程序使用TCP）<br>UDP： 用户数据报协议(User Datagram Protocol)<br>这是提供给用户进程的无连接协议，用于传送数据而不执行正确性检查。<br>FTP： 文件传输协议(File Transfer Protocol)<br>允许用户以文件操作的方式（文件的增、删、改、查、传送等）与另一主机相互通信。<br>SMTP： 简单邮件传送协议(Simple Mail Transfer Protocol)<br>SMTP协议为系统之间传送电子邮件。<br>TELNET：终端协议(Telnet Terminal Procotol)<br>允许用户以虚终端方式访问远程主机<br>HTTP： 超文本传输协议(Hypertext Transfer Procotol)<br>TFTP: 简单文件传输协议(Trivial File Transfer Protocol)<br><br><strong><wbr>2、TCP/IP特点</strong><wbr><br>TCP/IP协议的核心部分是传输层协议(TCP、UDP)，网络层协议(IP)和物理接口层，这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时，编程界面有两种形式：一、是由内核心直接提供的系统调用；二、使用以库函数方式提供的各种函数。前者为核内实现，后者为核外实现。用户服务要通过核外的应用程序才能实现，所以要使用套接字(socket)来实现。<br>图1.2是TCP/IP协议核心与应用程序关系图。<br><wbr><img style="VERTICAL-ALIGN: baseline! important" height=279 src="http://www.vckbase.com/document/journal/vckbase16/images/winsock1_2.gif" width=344 border=0><wbr> <br>(图1.2)<br><br><strong><wbr><wbr><img style="VERTICAL-ALIGN: baseline! important" height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14 border=0><wbr> 二、专用术语</strong><wbr><br>1、套接字<br>它是网络的基本构件。它是可以被命名和寻址的通信端点，使用中的每一个套接字都有其类型和一个与之相连听进程。套接字存在通信区域（通信区域又称地址簇）中。套接字只与同一区域中的套接字交换数据（跨区域时，需要执行某和转换进程才能实现）。WINDOWS 中的套接字只支持一个域——网际域。套接字具有类型。<br>WINDOWS SOCKET 1.1 版本支持两种套接字：流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM) 2、WINDOWS SOCKETS 实现<br>一个WINDOWS SOCKETS 实现是指实现了WINDOWS SOCKETS规范所描述的全部功能的一套软件。一般通过DLL文件来实现<br>3、阻塞处理例程<br>阻塞处理例程(blocking hook,阻塞钩子)是WINDOWS SOCKETS实现为了支持阻塞套接字函数调用而提供的一种机制。<br>4、多址广播（multicast，多点传送或组播）<br>是一种一对多的传输方式，传输发起者通过一次传输就将信息传送到一组接收者，与单点传送<br>(unicast)和广播(Broadcast)相对应。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52261.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:59 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52261.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ARM+LINUX学习</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52260.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:58:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52260.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52260.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52260.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52260.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52260.html</trackback:ping><description><![CDATA[ARM+LINUX路线，主攻嵌入式Linux操作系统及其上应用软件开发目标： <br><br>　　（1） 掌握主流嵌入式微处理器的结构与原理（初步定为arm9） <br><br>　　（2） 必须掌握一个嵌入式操作系统 （初步定为uclinux或linux,版本待定） <br><br>　　（3） 必须熟悉嵌入式软件开发流程并至少做一个嵌入式软件项目。 <br><br>　　从事嵌入式软件开发的好处是： <br><br>　　（1）目前国内外这方面的人都很稀缺。这一领域入门门槛较高，所以非专业IT人员很难切入这一领域；另一方面，是因为这一领域较新，目前发展太快，大多数人无条件接触。 <br><br>　　（2）与企业计算等应用软件不同，嵌入式领域人才的工作强度通常低一些（但收入不低）。 <br><br>　　（3）哪天若想创业，搞自已的产品，嵌入式不像应用软件那样容易被盗版。硬件设计一般都是请其它公司给订做（这叫&#8220;贴牌&#8221;：OEM），都是通用的硬件，我们只管设计软件就变成自己的产品了。 <br><br>　　（4）兴趣所在，这是最主要的。 <br><br>　　从事嵌入式软件开发的缺点是： <br><br>　　（1） 入门起点较高，所用到的技术往往都有一定难度，若软硬件基础不好，特别是操作系统级软件功底不深，则可能不适于此行。 <br><br>　　（2）这方面的企业数量要远少于企业计算类企业。 <br><br>　　（3）有少数公司经常要硕士以上的人搞嵌入式，主要是基于嵌入式的难度。但大多数公司也并无此要求，只要有经验即可。 <br><br>　　（4）平台依托强，换平台比较辛苦。 <br><br>　　兴趣的由来： <br><br>　　1、成功观念不同，不虚度此生，就是我的成功。 <br><br>　　2、喜欢思考，挑战逻辑思维。 <br><br>　　3、喜欢C <br><br>　　C是一种能发挥思维极限的语言。关于C的精神的一些方面可以被概述成短句如下： <br><br>　　相信程序员。 <br><br>　　不要阻止程序员做那些需要去做的。 <br><br>　　保持语言短小精干。 <br><br>　　一种方法做一个操作。 <br><br>　　使得它运行的够快，尽管它并不能保证将是可移植的。 <br><br>　　4、喜欢底层开发，讨厌vb类开发工具（并不是说vb不好）。 <br><br>　　5、发展前景好，适合创业，不想自己要死了的时候还是一个工程师。 <br><br>　　方法步骤： <br><br>　　1、基础知识： <br><br>　　目的：能看懂硬件工作原理，但重点在嵌入式软件，特别是操作系统级软件，那将是我的优势。 <br><br>　　科目：数字电路、计算机组成原理、嵌入式微处理器结构。 <br><br>　　汇编语言、C/C++、编译原理、离散数学。 <br><br>　　数据结构和算法、操作系统、软件工程、网络、数据库。 <br><br>　　方法：虽科目众多，但都是较简单的基础，且大部分已掌握。不一定全学，可根据需要选修。 <br><br>　　主攻书籍：the c++ programming language（一直没时间读）、数据结构-C2。 <br><br>　　2、学习linux： <br><br>　　目的：深入掌握linux系统。 <br><br>　　方法：使用linux—〉linxu系统编程开发—〉驱动开发和分析linux内核。先看深，那主讲原理。看几遍后，看情景分析，对照深看，两本交叉，深是纲，情是目。剖析则是0.11版，适合学习。最后深入代码。 <br><br>　　主攻书籍：linux内核完全剖析、unix环境高级编程、深入理解linux内核、情景分析和源代。 <br><br>　　3、学习嵌入式linux： <br><br>　　目的：掌握嵌入式处理器其及系统。 <br><br>　　方法：（1）嵌入式微处理器结构与应用：直接arm原理及汇编即可，不要重复x86。 <br><br>　　（2）嵌入式操作系统类：ucOS/II简单，开源，可供入门。而后深入研究uClinux。 <br><br>　　（3）必须有块开发板（arm9以上），有条件可参加培训（进步快，能认识些朋友）。 <br><br>　　主攻书籍：毛德操的《嵌入式系统》及其他arm9手册与arm汇编指令等。 <br><br>　　4、深入学习： <br><br>　　A、数字图像压缩技术：主要是应掌握MPEG、mp3等编解码算法和技术。 <br><br>　　B、通信协议及编程技术：TCP/IP协议、802.11，Bluetooth，GPRS、GSM、CDMA等。 <br><br>　　C、网络与信息安全技术：如加密技术，数字证书CA等。 <br><br>　　D、DSP技术：Digital Signal Process，DSP处理器通过硬件实现数字信号处理算法。 <br><br>　　说明：太多细节未说明，可根据实际情况调整。重点在于1、3，不必完全按照顺序作。对于学习c++，理由是c++不只是一种语言，一种工具，她还是一种艺术，一种文化，一种哲学理念、但不是拿来炫耀得东西。对于linux内核，学习编程，读一些优秀代码也是有必要的。 <br><br>　　注意：　要学会举一反多，有强大的基础，很多东西简单看看就能会。想成为合格的程序员，前提是必须熟练至少一种编程语言，并具有良好的逻辑思维。一定要理论结合实践。 <br><br>　　不要一味钻研技术，虽然挤出时间是很难做到的，但还是要留点余地去完善其他的爱好，比如宇宙，素描、机械、管理，心理学、游戏、科幻电影。还有一些不愿意做但必须要做的！ <br><br>　　技术是通过编程编程在编程编出来的。永远不要梦想一步登天，不要做浮躁的人，不要觉得路途漫上。而是要编程编程在编程，完了在编程，在编程！等机会来了在创业（不要相信有奇迹发生，盲目创业很难成功，即便成功了发展空间也不一定很大）。 <br><br>　　嵌入式书籍推荐 <br><br>　　Linux基础 <br><br>　　1、《Linux与Unix Shell 编程指南》 <br><br>　　C语言基础 <br><br>　　1、《C Primer Plus，5th Edition》【美】Stephen Prata着 <br><br>　　2、《The C Programming Language, 2nd Edition》【美】Brian W. Kernighan David M. Rithie（K &amp; R）着 <br><br>　　3、《Advanced Programming in the UNIX Environment，2nd Edition》（APUE） <br><br>　　4、《嵌入式Linux应用程序开发详解》 <br><br>　　Linux内核 <br><br>　　1、《深入理解Linux内核》（第三版） <br><br>　　2、《Linux内核源代码情景分析》毛德操 胡希明着 <br><br>　　研发方向 <br><br>　　1、《UNIX Network Programming》（UNP） <br><br>　　2、《TCP/IP详解》 <br><br>　　3、《Linux内核编程》 <br><br>　　4、《Linux设备驱动开发》（LDD） <br><br>　　硬件基础 <br><br>　　1、《ARM体系结构与编程》杜春雷着 <br><br>　　2、S3C2410 Datasheet <br><br>　　英语基础 <br><br>　　1、《计算机与通信专业英语》 <br><br>　　系统教程 <br><br>　　1、《嵌入式系统――体系结构、编程与设计》 <br><br>　　2、《嵌入式系统――采用公开源代码和StrongARM/Xscale处理器》毛德操 胡希明着 <br><br>　　3、《Building Embedded Linux Systems》 <br><br>　　理论基础 <br><br>　　1、《算法导论》 <br><br>　　2、《数据结构（C语言版）》 <br><br>　　3、《计算机组织与体系结构?性能分析》 <br><br>　　4、《深入理解计算机系统》【美】Randal E. Bryant David O'Hallaron着 <br><br>　　5、《操作系统：精髓与设计原理》 <br><br>　　6、《编译原理》 <br><br>　　7、《数据通信与计算机网络》 <br><br>　　8、《数据压缩原理与应用》 <br><br>　　C语言书籍推荐 <br><br>　　1. The C programming language <br><br>　　《Ｃ程序设计语言》 <br><br>　　2. Pointers on C <br><br>　　《Ｃ和指针》 <br><br>　　3. C traps and pitfalls <br><br>　　《Ｃ陷阱与缺陷》 <br><br>　　4. Expert C Lanuage <br><br>　　《专家Ｃ编程》 <br><br>　　5. Writing Clean Code <br><br>　　-----Microsoft Techiniques for Developing Bug-free C Programs <br><br>　　《编程精粹--Microsoft 编写优质无错Ｃ程序秘诀》 <br><br>　　6. Programming Embedded Systems in C and C++ <br><br>　　《嵌入式系统编程》 <br><br>　　7.《C语言嵌入式系统编程修炼》 <br><br>　　8.《高质量C++/C编程指南》林锐 <br><br>　　尽可能多的编码，要学好C，不能只注重C本身。算法，架构方式等都很重要。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52260.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:58 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52260.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++数组名与指针区别深入探索</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52258.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:57:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52258.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52258.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52258.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52258.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52258.html</trackback:ping><description><![CDATA[指针是C/C++语言的特色，而数组名与指针有太多的相似，甚至很多时候，数组名可以作为指针使用。于是乎，很多程序设计者就被搞糊涂了。而许多的大学老师，他们在C语言的教学过程中也错误得给学生讲解："数组名就是指针"。很幸运，我的大学老师就是其中之一。时至今日，我日复一日地进行着C/C++项目的开发，而身边还一直充满这样的程序员，他们保留着"数组名就是指针"的误解。<br><br>　　想必这种误解的根源在于国内某著名的C程序设计教程。如果这篇文章能够纠正许多中国程序员对数组名和指针的误解，笔者就不甚欣慰了。借此文，笔者站在无数对知识如饥似渴的中国程序员之中，深深寄希望于国内的计算机图书编写者们，能以"深入探索"的思维方式和精益求精的认真态度来对待图书编写工作，但愿市面上多一些融入作者思考结晶的心血之作！<br><br>　　魔幻数组名<br><br>　　请看程序（本文程序在WIN32平台下编译）：<br><br>1. #include &lt;iostream.h&gt;<br>2. int main(int argc, char* argv[])<br>3. {<br>4. 　char str[10];<br>5. 　char *pStr = str;<br>6. 　cout &lt;&lt; sizeof(str) &lt;&lt; endl;<br>7. 　cout &lt;&lt; sizeof(pStr) &lt;&lt; endl;<br>8. 　return 0;<br>9. } <br><br>　　1、数组名不是指针<br><br>　　我们先来推翻"数组名就是指针"的说法，用反证法。<br><br>　　证明　数组名不是指针<br><br>　　假设：数组名是指针；<br><br>　　则：pStr和str都是指针；<br><br>　　因为：在WIN32平台下，指针长度为4；<br><br>　　所以：第6行和第7行的输出都应该为4；<br><br>　　实际情况是：第6行输出10，第7行输出4；<br><br>　　所以：假设不成立，数组名不是指针<br><br>　　2、数组名神似指针<br><br>　　上面我们已经证明了数组名的确不是指针，但是我们再看看程序的第5行。该行程序将数组名直接赋值给指针，这显得数组名又的确是个指针！<br><br>　　我们还可以发现数组名显得像指针的例子：<br><br>1. #include &lt;string.h&gt;<br>2. #include &lt;iostream.h&gt;<br>3. int main(int argc, char* argv[])<br>4. {<br>5. 　char str1[10] = "I Love U";<br>6. 　char str2[10]; <br>7. 　strcpy(str2,str1);<br>8. 　cout &lt;&lt; "string array 1: " &lt;&lt; str1 &lt;&lt; endl;<br>9. 　cout &lt;&lt; "string array 2: " &lt;&lt; str2 &lt;&lt; endl;<br>10.　 return 0;<br>11. } <br><br>　　标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针，而我们在调用中传给它的却是两个数组名！函数输出：<br><br>string array 1: I Love U<br>string array 2: I Love U <br><br>　　数组名再一次显得像指针！<br><br>　　既然数组名不是指针，而为什么到处都把数组名当指针用？于是乎，许多程序员得出这样的结论：数组名（主）是（谓）不是指针的指针（宾）。<br><br>　　整个一魔鬼。<br><br>　　揭密数组名<br><br>　　现在到揭露数组名本质的时候了，先给出三个结论：<br><br>　　(1)数组名的内涵在于其指代实体是一种数据结构，这种数据结构就是数组；<br><br>　　(2)数组名的外延在于其可以转换为指向其指代实体的指针，而且是一个指针常量；<br><br>　　(3)指向数组的指针则是另外一种变量类型（在WIN32平台下，长度为4），仅仅意味着数组的存放地址！<br><br>　　1、数组名指代一种数据结构：数组<br><br>　　现在可以解释为什么第1个程序第6行的输出为10的问题，根据结论1，数组名str的内涵为一种数据结构，即一个长度为10的char型数组，所以sizeof(str)的结果为这个数据结构占据的内存大小：10字节。<br><br>　　再看：<br><br>1. int intArray[10];<br>2. cout &lt;&lt; sizeof(intArray) ; <br><br>　　第2行的输出结果为40（整型数组占据的内存空间大小）。<br><br>　　如果C/C++程序可以这样写：<br><br>1. int[10] intArray;<br>2. cout &lt;&lt; sizeof(intArray) ; <br><br>　　我们就都明白了，intArray定义为int[10]这种数据结构的一个实例，可惜啊，C/C++目前并不支持这种定义方式。<br><br>　　2、数组名可作为指针常量<br><br>　　根据结论2，数组名可以转换为指向其指代实体的指针，所以程序1中的第5行数组名直接赋值给指针，程序2第7行直接将数组名作为指针形参都可成立。<br><br>　　下面的程序成立吗？<br><br>1. int intArray[10];<br>2. intArray++; <br><br>　　读者可以编译之，发现编译出错。原因在于，虽然数组名可以转换为指向其指代实体的指针，但是它只能被看作一个指针常量，不能被修改。 <br><br>　　而指针，不管是指向结构体、数组还是基本数据类型的指针，都不包含原始数据结构的内涵，在WIN32平台下，sizeof操作的结果都是4。<br>顺便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数，而实际上，它是一个操作符，不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数，因为函数接纳形参（一个变量），世界上没有一个C/C++函数接纳一个数据类型（如int）为"形参"。<br><br>　　3、数据名可能失去其数据结构内涵 <br><br>　　到这里似乎数组名魔幻问题已经宣告圆满解决，但是平静的湖面上却再次掀起波浪。请看下面一段程序：<br><br>1. #include &lt;iostream.h&gt;<br>2. void arrayTest(char str[])<br>3. {<br>4. 　cout &lt;&lt; sizeof(str) &lt;&lt; endl;<br>5. }<br>6. int main(int argc, char* argv[])<br>7. {<br>8. 　char str1[10] = "I Love U";<br>9. 　arrayTest(str1); <br>10.　 return 0;<br>11. } <br><br>　　程序的输出结果为4。不可能吧？<br><br>　　一个可怕的数字，前面已经提到其为指针的长度!<br><br>　　结论1指出，数据名内涵为数组这种数据结构，在arrayTest函数体内，str是数组名，那为什么sizeof的结果却是指针的长度？这是因为：<br><br>　　(1)数组名作为函数形参时，在函数体内，其失去了本身的内涵，仅仅只是一个指针；<br><br>　　(2)很遗憾，在失去其内涵的同时，它还失去了其常量特性，可以作自增、自减等操作，可以被修改。<br><br>　　所以，数据名作为函数形参时，其全面沦落为一个普通指针！它的贵族身份被剥夺，成了一个地地道道的只拥有4个字节的平民。<br><br>　　以上就是结论4。<br><br>　　结束语<br><br>　　最后，笔者再次表达深深的希望，愿我和我的同道中人能够真正以谨慎的研究态度来认真思考开发中的问题，这样才能在我们中间产生大师级的程序员，顶级的开发书籍。每次拿着美国鬼子的开发书籍，我们不免发出这样的感慨：我们落后太远了。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<div class=clear id=paperBottom></div>
<div class="mode_table_mains none" style="BORDER-TOP: 1px dashed">本文标签：</div>
<div class="mode_sign showsign" id=verySign>
<table class=contentTable cellSpacing=0 cellPadding=0>
    <tbody>
        <tr>
            <td></td>
        </tr>
    </tbody>
</table>
</div>
<img src ="http://www.cnitblog.com/ison/aggbug/52258.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:57 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52258.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内存映射</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52259.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:57:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52259.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52259.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52259.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52259.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52259.html</trackback:ping><description><![CDATA[所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如，在 SA-1100 CPU 中，从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间，而在 Samsung S3C44B0X CPU 中，从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM，但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说，具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上，而让剩下的那部分预留 RAM 地址空间处于未使用状态。由于上述这个事实，因此 Boot Loader 的 stage2 必须在它想干点什么 (比如，将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况，也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元，哪些是处于 "unused" 状态的。 <br>－－－ <br>.如果清楚知道自己RAM的用法，其实内存映射是可以不用加上去的。 在指挥权移交给kernel之前，bootloader可是老大。想怎么样就怎么样。另外多说一句，kernel中对RAM的使用也有类似的情况，在相应的include /arch 下面的header file中会有当前系统RAM的起始地址，大小，bus-width等等信息。 <br>当然，也可以采用动态检测，当系统启动了之后自动检查RAM的参数。 灵活＋代码多且耗时。在嵌入式系统中通常资源紧张的情况下，很少这么做（as far as I know）。 <br>
<img src ="http://www.cnitblog.com/ison/aggbug/52259.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:57 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52259.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>嵌入式开发—C语言面试题 </title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52257.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:56:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52257.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52257.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52257.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52257.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52257.html</trackback:ping><description><![CDATA[<font color=#0000ff>预处理器（Preprocessor）<wbr style="LINE-HEIGHT: 1.3em"></font> <br><strong><wbr>1. 用预处理指令#define 声明一个常数，用以表明1年中有多少秒（忽略闰年问题）</strong><wbr> <br>#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL <br>我在这想看到几件事情： <br>1). #define 语法的基本知识（例如：不能以分号结束，括号的使用，等等） <br>2). 懂得预处理器将为你计算常数表达式的值，因此，直接写出你是如何计算一年中有多少秒而不是计算出实际的值，是更清晰而没有代价的。 <br>3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。 <br>4). 如果你在你的表达式中用到UL（表示无符号长整型），那么你有了一个好的起点。记住，第一印象很重要。 <br><strong><wbr>2. 写一个&#8220;标准&#8221;宏MIN，这个宏输入两个参数并返回较小的一个。</strong><wbr> <br>#define MIN(A,B) ((A) &lt;= (B) (A) : )) <br>这个测试是为下面的目的而设的： <br>1). 标识#define在宏中应用的基本知识。这是很重要的，因为直到嵌入(inline)操作符变为标准C的一部分，宏是方便产生嵌入代码的唯一方法，对于嵌入式系统来说，为了能达到要求的性能，嵌入代码经常是必须的方法。 <br>2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码，了解这个用法是很重要的。 <br>3). 懂得在宏中小心地把参数用括号括起来 <br>4). 我也用这个问题开始讨论宏的副作用，例如：当你写下面的代码时会发生什么事？ <br>least = MIN(*p++, b); <br><strong><wbr>3. 预处理器标识#error的目的是什么？</strong><wbr> <br>如果你不知道答案，请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种 <br>问题的答案。当然如果你不是在找一个书呆子，那么应试者最好希望自己不要知道答案。 <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>死循环（Infinite loops）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>4. 嵌入式系统中经常要用到无限循环，你怎么样用C编写死循环呢？</strong><wbr> <br>这个问题用几个解决方案。我首选的方案是： <br>while(1) { } <br>一些程序员更喜欢如下方案： <br>for(;;) { } <br>这个实现方式让我为难，因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案，我将用这个作为一个机会去探究他们这样做的 <br>基本原理。如果他们的基本答案是：&#8220;我被教着这样做，但从没有想到过为什么。&#8221;这会给我留下一个坏印象。 <br>第三个方案是用 goto <br>Loop: <br>... <br>goto Loop; <br>应试者如给出上面的方案，这说明或者他是一个汇编语言程序员（这也许是好事）或者他是一个想进入新领域的BASIC/FORTRAN程序员。 <br><font style="LINE-HEIGHT: 1.3em" color=#0033ff>数据声明（Data declarations）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>5. 用变量a给出下面的定义</strong><wbr> <br>a) 一个整型数（An integer） <br>b) 一个指向整型数的指针（A pointer to an integer） <br>c) 一个指向指针的的指针，它指向的指针是指向一个整型数（A pointer to a pointer to an integer） <br>d) 一个有10个整型数的数组（An array of 10 integers） <br>e) 一个有10个指针的数组，该指针是指向一个整型数的（An array of 10 pointers to integers） <br>f) 一个指向有10个整型数数组的指针（A pointer to an array of 10 integers） <br>g) 一个指向函数的指针，该函数有一个整型参数并返回一个整型数（A pointer to a function that takes an integer as an argument and returns an integer） <br>h) 一个有10个指针的数组，该指针指向一个函数，该函数有一个整型参数并返回一个整型数（ An array of ten pointers to functions that take an integer argument and return an integer ） <br>答案是： <br>a) int a; // An integer <br>b) int *a; // A pointer to an integer <br>c) int **a; // A pointer to a pointer to an integer <br>d) int a[10]; // An array of 10 integers <br>e) int *a[10]; // An array of 10 pointers to integers <br>f) int (*a)[10]; // A pointer to an array of 10 integers <br>g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer <br>h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer <br>人们经常声称这里有几个问题是那种要翻一下书才能回答的问题，我同意这种说法。当我写这篇文章时，为了确定语法的正确性，我的确查了一下书。 <br>但是当我被面试的时候，我期望被问到这个问题（或者相近的问题）。因为在被面试的这段时间里，我确定我知道这个问题的答案。应试者如果不知道 <br>所有的答案（或至少大部分答案），那么也就没有为这次面试做准备，如果该面试者没有为这次面试做准备，那么他又能为什么出准备呢？ <br><font style="LINE-HEIGHT: 1.3em" color=#0033ff>Static</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>6. 关键字static的作用是什么？</strong><wbr> <br>这个简单的问题很少有人能回答完全。在C语言中，关键字static有三个明显的作用： <br>1). 在函数体，一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 <br>2). 在模块内（但在函数体外），一个被声明为静态的变量可以被模块内所用函数访问，但不能被模块外其它函数访问。它是一个本地的全局变量。 <br>3). 在模块内，一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是，这个函数被限制在声明它的模块的本地范围内使用。 <br>大多数应试者能正确回答第一部分，一部分能正确回答第二部分，同是很少的人能懂得第三部分。这是一个应试者的严重的缺点，因为他显然不懂得本地化数据和代码范围的好处和重要性。 <br><font style="LINE-HEIGHT: 1.3em" color=#0909f7>Const</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>7．关键字const是什么含意？</strong><wbr> <br>我只要一听到被面试者说：&#8220;const意味着常数&#8221;，我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法，因此ESP(译者：Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么. <br>如果你从没有读到那篇文章，只要能说出const意味着&#8220;只读&#8221;就可以了。尽管这个答案不是完全的答案，但我接受它作为一个正确的答案。（如果你想知道更详细的答案，仔细读一下Saks的文章吧。）如果应试者能正确回答这个问题，我将问他一个附加的问题：下面的声明都是什么意思？ <br>const int a; <br>int const a; <br>const int *a; <br>int * const a; <br>int const * a const; <br>前两个的作用是一样，a是一个常整型数。第三个意味着a是一个指向常整型数的指针（也就是，整型数是不可修改的，但指针可以）。第四个意思a是一个指向整型数的常指针（也就是说，指针指向的整型数是可以修改的，但指针是不可修改的）。最后一个意味着a是一个指向常整型数的常指针（也就是说，指针指向的整型数是不可修改的，同时指针也是不可修改的）。如果应试者能正确回答这些问题，那么他就给我留下了一个好印象。顺带提一句，也许你可能会问，即使不用关键字 const，也还是能很容易写出功能正确的程序，那么我为什么还要如此看重关键字const呢？我也如下的几下理由： <br>1). 关键字const的作用是为给读你代码的人传达非常有用的信息，实际上，声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾，你就会很快学会感谢这点多余的信息。（当然，懂得用const的程序员很少会留下的垃圾让别人来清理的。） <br>2). 通过给优化器一些附加的信息，使用关键字const也许能产生更紧凑的代码。 <br>3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数，防止其被无意的代码修改。简而言之，这样可以减少bug的出现。 <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>Volatile</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>8. 关键字volatile有什么含意 并给出三个不同的例子。</strong><wbr> <br>一个定义为volatile的变量是说这变量可能会被意想不到地改变，这样，编译器就不会去假设这个变量的值了。精确地说就是，优化器在用到这个变量时必须每次都小心地重新读取这个变量的值，而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子： <br>1). 并行设备的硬件寄存器（如：状态寄存器） <br>2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) <br>3). 多线程应用中被几个任务共享的变量 <br>回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道，所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。 <br>假设被面试者正确地回答了这是问题（嗯，怀疑这否会是这样），我将稍微深究一下，看一下这家伙是不是直正懂得volatile完全的重要性。 <br>1). 一个参数既可以是const还可以是volatile吗？解释为什么。 <br>2). 一个指针可以是volatile 吗？解释为什么。 <br>3). 下面的函数有什么错误： <br>int square(volatile int *ptr) <br>{ return *ptr * *ptr; <br>} 下面是答案： <br>1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 <br>2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 <br>3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方，但是，由于*ptr指向一个volatile型参数，编译器将产生类似下面的代码： <br>int square(volatile int *ptr) <br>{ int a,b; <br>a = *ptr; <br>b = *ptr; <br>return a * b; <br>} 由于*ptr的值可能被意想不到地该变，因此a和b可能是不同的。结果，这段代码可能返不是你所期望的平方值！正确的代码如下： <br>long square(volatile int *ptr) <br>{ int a; <br>a = *ptr; <br>return a * a; <br>} <br><font style="LINE-HEIGHT: 1.3em" color=#0909f7>位操作（Bit manipulation）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a，写两段代码，第一个设置a的bit 3，第二个清除a 的bit 3。在以上两个操作中，要保持其它位不变。</strong><wbr> <br>对这个问题有三种基本的反应 <br>1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 <br>2). 用bit fields。Bit fields是被扔到C语言死角的东西，它保证你的代码在不同编译器之间是不可移植的，同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序，它用到了bit fields因此完全对我无用，因为我的编译器用其它的方式来实现bit fields的。从道德讲：永远不要让一个非嵌入式的家伙粘实际硬件的边。 <br>3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法，是应该被用到的方法。最佳的解决方案如下： <br>#define BIT3 (0x1&lt;&lt;3) <br>static int a; <br>void set_bit3(void) <br>{ a |= BIT3; <br>} void clear_bit3(void) <br>{ a &amp;= ~BIT3; <br>} 一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数，这也是可以接受的。我希望看到几个要点：说明常数、|=和&amp;=~操作。 <br><strong><wbr>10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中，要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。</strong><wbr> <br>这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换（typecast）为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下： <br>int *ptr; <br>ptr = (int *)0x67a9; <br>*ptr = 0xaa55; <br>一个较晦涩的方法是： <br>*(int * const)(0x67a9) = 0xaa55; <br>即使你的品味更接近第二种方案，但我建议你在面试时使用第一种方案。 <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>中断（Interrupts）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>11. 中断是嵌入式系统中重要的组成部分，这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是，产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR)，请评论一下这段代码的。</strong><wbr> <br>__interrupt double compute_area (double radius) <br>{ double area = PI * radius * radius; <br>printf(" Area = %f", area); <br>return area; <br>} <br>这个函数有太多的错误了，以至让人不知从何说起了： <br>1). ISR 不能返回一个值。如果你不懂这个，那么你不会被雇用的。 <br>2). ISR 不能传递参数。如果你没有看到这一点，你被雇用的机会等同第一项。 <br>3). 在许多的处理器/编译器中，浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈，有些处理器/编译器就是不允许在ISR中做浮点运算。此外，ISR应该是短而有效率的，在ISR中做浮点运算是不明智的。 <br>4). 与第三点一脉相承，printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点，我不会太为难你的。不用说，如果你能得到后两点，那么你的被雇用前景越来越光明了。 <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>代码例子（Code examples）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>12 . 下面的代码输出是什么，为什么？</strong><wbr> <br><strong><wbr>void foo(void) <br>{ unsigned int a = 6; <br>int b = -20; <br>(a+b &gt; 6) puts("&gt; 6") : puts("&lt;= 6"); <br>}</strong><wbr> <br>这个问题测试你是否懂得C语言中的整数自动转换原则，我发现有些开发者懂得极少这些东西。不管如何，这无符号整型问题的答案是输出是&#8220;&gt;6&#8221;。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数，所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题，你也就到了得不到这份工作的边缘。 <br><strong><wbr>13. 评价下面的代码片断：</strong><wbr> <br><strong><wbr>unsigned int zero = 0; <br>unsigned int compzero = 0xFFFF; <br>/*1's complement of zero */</strong><wbr> <br><strong><wbr>对于一个int型不是16位的处理器为说，上面的代码是不正确的。应编写如下：</strong><wbr> <br><strong><wbr>unsigned int compzero = ~0;</strong><wbr> <br>这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里，好的嵌入式程序员非常准确地明白硬件的细节和它的局限，然而PC机程序往往把硬件作为一个无法避免的烦恼。 <br>到了这个阶段，应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好，那么这个测试就在这里结束了。但如果显然应试者做得不错，那么我就扔出下面的追加问题，这些问题是比较难的，我想仅仅非常优秀的应试者能做得不错。提出这些问题，我希望更多看到应试者应付问题的方法，而不是答案。不管如何，你就当是这个娱乐吧&#8230; <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>动态内存分配（Dynamic memory allocation）</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>14. 尽管不像非嵌入式计算机那么常见，嵌入式系统还是有从堆（heap）中动态分配内存的过程的。那么嵌入式系统中，动态分配内存可能发生的问题是什么？</strong><wbr> <br>这里，我期望应试者能提到内存碎片，碎片收集的问题，变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了（主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释），所有回过头看一下这些杂志吧！让应试者进入一种虚假的安全感觉后，我拿出这么一个小节目：下面的代码片段的输出是什么，为什么？ <br>char *ptr; <br>if ((ptr = (char *)malloc(0)) == NULL) <br>puts("Got a null pointer"); <br>else <br>puts("Got a valid pointer"); <br>这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc，得到了一个合法的指针之后，我才想到这个问题。这就是上面的代码，该代码的输出是&#8220;Got a valid pointer&#8221;。我用这个来开始讨论这样的一问题，看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要，但解决问题的方法和你做决定的基本原理更重要些。 <br><font style="LINE-HEIGHT: 1.3em" color=#0000ff>Typedef</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>15. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如，思考一下下面的例子：</strong><wbr> <br><strong><wbr>#define dPS struct s * <br>typedef struct s * tPS;</strong><wbr> <br>以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢？（如果有的话）为什么？ <br>这是一个非常微妙的问题，任何人答对这个问题（正当的原因）是应当被恭喜的。答案是：typedef更好。思考下面的例子： <br>dPS p1,p2; <br>tPS p3,p4; <br>第一个扩展为 <br>struct s * p1, p2; <br>上面的代码定义p1为一个指向结构的指，p2为一个实际的结构，这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif"><br><font color=#0000ff>晦涩的语法<wbr style="LINE-HEIGHT: 1.3em"></font> <br><strong><wbr>16. C语言同意一些令人震惊的结构,下面的结构是合法的吗，如果是它做些什么？ <br>int a = 5, b = 7, c; <br>c = a+++b;</strong><wbr> <br>这个问题将做为这个测验的一个愉快的结尾。不管你相不相信，上面的例子是完全合乎语法的。问题是编译器如何处理它？水平不高的编译作者实际上会争论这个问题，根据最处理原则，编译器应当能处理尽可能所有合法的用法。因此，上面的代码被处理成： <br>c = a++ + b; <br>因此, 这段代码持行后a = 6, b = 7, c = 12。 <br>如果你知道答案，或猜出正确答案，做得好。如果你不知道答案，我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格，代码的可读性，代码的可修改性的好的话题 <br>What will print out? <br>main() <br>{ char *p1=&#8220;name&#8221;; <br>char *p2; <br>p2=(char*)malloc(20); <br>memset (p2, 0, 20); <br>while(*p2++ = *p1++); <br>printf(&#8220;%sn&#8221;,p2); <br>} <br>Answer:empty string. <br>What will be printed as the result of the operation below: <br>main() <br>{ int x=20,y=35; <br>x=y++ + x++; <br>y= ++y + ++x; <br>printf(&#8220;%d%dn&#8221;,x,y); <br>} <br>Answer : 5794 <br>What will be printed as the result of the operation below: <br>main() <br>{ int x=5; <br>printf(&#8220;%d,%d,%dn&#8221;,x,x&lt; &lt;2,x&gt;&gt;2); <br>} <br>Answer: 5,20,1 <br>What will be printed as the result of the operation below: <br>#define swap(a,b) a=a+b;b=a-b;a=a-b; <br>void main() <br>{ int x=5, y=10; <br>swap (x,y); <br>printf(&#8220;%d %dn&#8221;,x,y); <br>swap2(x,y); <br>printf(&#8220;%d %dn&#8221;,x,y); <br>} <br>int swap2(int a, int b) <br>{ int temp; <br>temp=a; <br>b=a; <br>a=temp; <br>return 0; <br>} <br>Answer: 10, 5 <br>10, 5 <br>What will be printed as the result of the operation below: <br>main() <br>{ char *ptr = &#8221; Cisco Systems&#8221;; <br>*ptr++; printf(&#8220;%sn&#8221;,ptr); <br>ptr++; <br>printf(&#8220;%sn&#8221;,ptr); <br>} <br>Answer:Cisco Systems <br>isco systems <br>What will be printed as the result of the operation below: <br>main() <br>{ char s1[]=&#8220;Cisco&#8221;; <br>char s2[]= &#8220;systems&#8221;; <br>printf(&#8220;%s&#8221;,s1); <br>} Answer: Cisco <br>What will be printed as the result of the operation below: <br>main() <br>{ char *p1; <br>char *p2; <br>p1=(char *)malloc(25); <br>p2=(char *)malloc(25); <br>strcpy(p1,&#8221;Cisco&#8221;); <br>strcpy(p2,&#8220;systems&#8221;); <br>strcat(p1,p2); <br>printf(&#8220;%s&#8221;,p1); <br>} <br>Answer: Ciscosystems <br>The following variable is available in file1.c, who can access it?: <br>static int average; <br>Answer: all the functions in the file1.c can access the variable. <br>WHat will be the result of the following code? <br>#define TRUE 0 // some code <br>while(TRUE) <br>{ <br>// some code <br>} <br>Answer: This will not go into the loop as TRUE is defined as 0. <br>What will be printed as the result of the operation below: <br>int x; <br>int modifyvalue() <br>{ return(x+=10); <br>} int changevalue(int x) <br>{ return(x+=1); <br>} <br>void main() <br>{ int x=10; <br>x++; <br>changevalue(x); <br>x++; <br>modifyvalue(); <br>printf("First output:%dn",x); <br>x++; <br>changevalue(x); <br>printf("Second output:%dn",x); <br>modifyvalue(); <br>printf("Third output:%dn",x); <br>} <br>Answer: 12 , 13 , 13 <br>What will be printed as the result of the operation below: <br>main() <br>{ int x=10, y=15; <br>x = x++; <br>y = ++y; <br>printf(&#8220;%d %dn&#8221;,x,y); <br>} <br>Answer: 11, 16 <br>What will be printed as the result of the operation below: <br>main() <br>{ int a=0; <br>if(a==0) <br>printf(&#8220;Cisco Systemsn&#8221;); <br>printf(&#8220;Cisco Systemsn&#8221;); <br>} <br>Answer: Two lines with &#8220;Cisco Systems&#8221; will be printed. <br><font style="LINE-HEIGHT: 1.3em" color=#0033ff>再次更新C++相关题集</font><wbr style="LINE-HEIGHT: 1.3em"> <br><strong><wbr>1. 以下三条输出语句分别输出什么？[C易]</strong><wbr> <br>char str1[] = "abc"; <br>char str2[] = "abc"; <br>const char str3[] = "abc"; <br>const char str4[] = "abc"; <br>const char* str5 = "abc"; <br>const char* str6 = "abc"; <br>cout &lt;&lt; boolalpha &lt;&lt; ( str1==str2 ) &lt;&lt; endl; // 输出什么？ <br>cout &lt;&lt; boolalpha &lt;&lt; ( str3==str4 ) &lt;&lt; endl; // 输出什么？ <br>cout &lt;&lt; boolalpha &lt;&lt; ( str5==str6 ) &lt;&lt; endl; // 输出什么？ <br><strong><wbr>13. 非C++内建型别 A 和 B，在哪几种情况下B能隐式转化为A？[C++中等]</strong><wbr> <br>答： <br>a. class B : public A { &#8230;&#8230;} // B公有继承自A，可以是间接继承的 <br>b. class B { operator A( ); } // B实现了隐式转化为A的转化 <br>c. class A { A( const B&amp; ); } // A实现了non-explicit的参数为B（可以有其他带默认值的参数）构造函数 <br>d. A&amp; operator= ( const A&amp; ); // 赋值操作，虽不是正宗的隐式类型转换，但也可以勉强算一个 <br><strong><wbr>12. 以下代码中的两个sizeof用法有问题吗？[C易]</strong><wbr> <br>void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母 <br>{ for( size_t i=0; i&lt;sizeof(str)/sizeof(str[0]); ++i ) <br>if( 'a'&lt;=str[i] &amp;&amp; str[i]&lt;='z' ) <br>str[i] -= ('a'-'A' ); <br>} char str[] = "aBcDe"; <br>cout &lt;&lt; "str字符长度为: " &lt;&lt; sizeof(str)/sizeof(str[0]) &lt;&lt; endl; <br>UpperCase( str ); <br>cout &lt;&lt; str &lt;&lt; endl; <br><strong><wbr>7. 以下代码有什么问题？[C难]</strong><wbr> <br>void char2Hex( char c ) // 将字符以16进制表示 <br>{ char ch = c/0x10 + '0'; if( ch &gt; '9' ) ch += ('A'-'9'-1); <br>char cl = c%0x10 + '0'; if( cl &gt; '9' ) cl += ('A'-'9'-1); <br>cout &lt;&lt; ch &lt;&lt; cl &lt;&lt; ' '; <br>} char str[] = "I love 中国"; <br>for( size_t i=0; i&lt;strlen(str); ++i ) <br>char2Hex( str[i] ); <br>cout &lt;&lt; endl; <br><strong><wbr>4. 以下代码有什么问题？[C++易]</strong><wbr> <br>struct Test <br>{ Test( int ) {} <br>Test() {} <br>void fun() {} <br>}; <br>void main( void ) <br>{ Test a(1); <br>a.fun(); <br>Test b(); <br>b.fun(); <br>} <br><strong><wbr>5. 以下代码有什么问题？[C++易]</strong><wbr> <br>cout &lt;&lt; (true?1:"1") &lt;&lt; endl; <br><strong><wbr>8. 以下代码能够编译通过吗，为什么？[C++易]</strong><wbr> <br>unsigned int const size1 = 2; <br>char str1[ size1 ]; <br>unsigned int temp = 0; <br>cin &gt;&gt; temp; <br>unsigned int const size2 = temp; <br>char str2[ size2 ]; <br><strong><wbr>9. 以下代码中的输出语句输出0吗，为什么？[C++易]</strong><wbr> <br>struct CLS <br>{ int m_i; <br>CLS( int i ) : m_i(i) {} <br>CLS() <br>{ CLS(0); <br>} }; <br>CLS obj; <br>cout &lt;&lt; obj.m_i &lt;&lt; endl; <br><strong><wbr>10. C++中的空类，默认产生哪些类成员函数？[C++易]</strong><wbr> <br>答： <br>class Empty <br>{ public: <br>Empty(); // 缺省构造函数 <br>Empty( const Empty&amp; ); // 拷贝构造函数 <br>~Empty(); // 析构函数 <br>Empty&amp; operator=( const Empty&amp; ); // 赋值运算符 <br>Empty* operator&amp;(); // 取址运算符 <br>const Empty* operator&amp;() const; // 取址运算符 const <br>}; <br><strong><wbr>3. 以下两条输出语句分别输出什么？[C++难]</strong><wbr> <br>float a = 1.0f; <br>cout &lt;&lt; (int)a &lt;&lt; endl; <br>cout &lt;&lt; (int&amp;)a &lt;&lt; endl; <br>cout &lt;&lt; boolalpha &lt;&lt; ( (int)a == (int&amp;)a ) &lt;&lt; endl; // 输出什么？ <br>float b = 0.0f; <br>cout &lt;&lt; (int)b &lt;&lt; endl; <br>cout &lt;&lt; (int&amp;)b &lt;&lt; endl; <br>cout &lt;&lt; boolalpha &lt;&lt; ( (int)b == (int&amp;)b ) &lt;&lt; endl; // 输出什么？ <br><strong><wbr>2. 以下反向遍历array数组的方法有什么错误？[STL易]</strong><wbr> <br>vector array; <br>array.push_back( 1 ); <br>array.push_back( 2 ); <br>array.push_back( 3 ); <br>for( vector::size_type i=array.size()-1; i&gt;=0; --i ) // 反向遍历array数组 <br>{ cout &lt;&lt; array[i] &lt;&lt; endl; <br>} <br><strong><wbr>6. 以下代码有什么问题？[STL易]</strong><wbr> <br>typedef vector IntArray; <br>IntArray array; <br>array.push_back( 1 ); <br>array.push_back( 2 ); <br>array.push_back( 2 ); <br>array.push_back( 3 ); <br>// 删除array数组中所有的2 <br>for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor ) <br>{ if( 2 == *itor ) array.erase( itor ); <br>} <br><strong><wbr>11. 写一个函数，完成内存之间的拷贝。[考虑问题是否全面]</strong><wbr> <br>答： <br>void* mymemcpy( void *dest, const void *src, size_t count )&nbsp;&nbsp; <br>{&nbsp;&nbsp; <br>char* pdest = static_cast&lt;char*&gt;( dest );&nbsp;&nbsp; <br>const char* psrc = static_cast&lt;const char*&gt;( src );&nbsp;&nbsp; <br>if( pdest&gt;psrc &amp;&amp; pdest&lt;psrc+cout ) 能考虑到这种情况就行了&nbsp;&nbsp; <br>{&nbsp;&nbsp; <br>for( size_t i=count-1; i!=-1; --i )&nbsp;&nbsp; <br>pdest[i] = psrc[i];&nbsp;&nbsp; <br>}&nbsp;&nbsp; <br>else&nbsp;&nbsp;<br>{&nbsp;&nbsp; <br>for( size_t i=0; i&lt;count; ++i )&nbsp;&nbsp; <br>pdest[i] = psrc[i];&nbsp;&nbsp; <br>}&nbsp;&nbsp; <br>return dest;&nbsp;&nbsp; <br>}&nbsp;&nbsp; <br>int main( void )&nbsp;&nbsp; <br>{&nbsp;&nbsp; <br>char str[] = "0123456789";&nbsp;&nbsp; <br>mymemcpy( str+1, str+0, 9 );&nbsp;&nbsp; <br>cout &lt;&lt; str &lt;&lt; endl;&nbsp;&nbsp; <br>&nbsp;&nbsp;<br>system( "Pause" );&nbsp;&nbsp; <br>return 0;&nbsp;&nbsp; <br>}<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52257.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:56 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52257.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言之详解#ifdef等宏</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52256.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 08:39:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52256.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52256.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52256.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52256.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52256.html</trackback:ping><description><![CDATA[这几个宏是为了进行条件编译。一般情况下，源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译，也就是对一部分内容指定编译的条件，这就是&#8220;条件编译&#8221;。有时，希望当满足某条件时对一组语句进行编译，而当条件不满足时则编译另一组语句。 <br>&nbsp;&nbsp;&nbsp;&nbsp;条件编译命令最常见的形式为： <br>&nbsp;&nbsp;&nbsp;&nbsp;#ifdef 标识符 <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段1 <br>&nbsp;&nbsp;&nbsp;&nbsp;#else <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段2 <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;它的作用是：当标识符已经被定义过(一般是用#define命令定义)，则对程序段1进行编译，否则编译程序段2。 <br>&nbsp;&nbsp;&nbsp;&nbsp;其中#else部分也可以没有，即： <br>&nbsp;&nbsp;&nbsp;&nbsp;#ifdef <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段1 <br>&nbsp;&nbsp;&nbsp;&nbsp;#denif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;这里的&#8220;程序段&#8221;可以是语句组，也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行，而不同的计算机又有一定的差异。例如，我们有一个数据类型，在Windows平台中，应该使用long类型表示，而在其他平台应该使用float表示，这样往往需要对源程序作必要的修改，这就降低了程序的通用性。可以用以下的条件编译： <br>&nbsp;&nbsp;&nbsp;&nbsp;#ifdef WINDOWS <br>&nbsp;&nbsp;&nbsp;&nbsp;#define MYTYPE long <br>&nbsp;&nbsp;&nbsp;&nbsp;#else <br>&nbsp;&nbsp;&nbsp;&nbsp;#define MYTYPE float <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;如果在Windows上编译程序，则可以在程序的开始加上 <br>&nbsp;&nbsp;&nbsp;&nbsp;#define WINDOWS <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;这样则编译下面的命令行： <br>&nbsp;&nbsp;&nbsp;&nbsp;#define MYTYPE long <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;如果在这组条件编译命令之前曾出现以下命令行： <br>&nbsp;&nbsp;&nbsp;&nbsp;#define WINDOWS 0 <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;则预编译后程序中的MYTYPE都用float代替。这样，源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况，可以根据此思路设计出其它的条件编译。 <br>&nbsp;&nbsp;&nbsp;&nbsp;例如，在调试程序时，常常希望输出一些所需的信息，而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段： <br>&nbsp;&nbsp;&nbsp;&nbsp;#ifdef DEBUG <br>&nbsp;&nbsp;&nbsp;&nbsp;print ("device_open(%p)\n", file); <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;如果在它的前面有以下命令行： <br>&nbsp;&nbsp;&nbsp;&nbsp;#define DEBUG <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;则在程序运行时输出file指针的值，以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的，即在调试时加一批printf语句，调试后一一将printf语句删除去。的确，这是可以的。但是，当调试时加的printf语句比较多时，修改的工作量是很大的。用条件编译，则不必一一删改printf语句，只需删除前面的一条&#8220;#define DEBUG&#8221;命令即可，这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用，即起统一控制的作用，如同一个&#8220;开关&#8221;一样。 <br>&nbsp;&nbsp;&nbsp;&nbsp;有时也采用下面的形式： <br>&nbsp;&nbsp;&nbsp;&nbsp;#ifndef 标识符 <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段1 <br>&nbsp;&nbsp;&nbsp;&nbsp;#else <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段2 <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;只是第一行与第一种形式不同：将&#8220;ifdef&#8221;改为&#8220;ifndef&#8221;。它的作用是：若标识符未被定义则编译程序段1，否则编译程序段2。这种形式与第一种形式的作用相反。 <br>&nbsp;&nbsp;&nbsp;&nbsp;以上两种形式用法差不多，根据需要任选一种，视方便而定。 <br>&nbsp;&nbsp;&nbsp;&nbsp;还有一种形式，就是#if后面的是一个表达式，而不是一个简单的标识符： <br>&nbsp;&nbsp;&nbsp;&nbsp;#if 表达式 <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段1 <br>&nbsp;&nbsp;&nbsp;&nbsp;#else <br>&nbsp;&nbsp;&nbsp;&nbsp;程序段2 <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;它的作用是：当指定的表达式值为真（非零）时就编译程序段1，否则编译程序段2。可以事先给定一定条件，使程序在不同的条件下执行不同的功能。 <br>&nbsp;&nbsp;&nbsp;&nbsp;例如：输入一行字母字符，根据需要设置条件编译，使之能将字母全改为大写输出，或全改为小写字母输出。 <br>&nbsp;&nbsp;&nbsp;&nbsp;#define LETTER 1 <br>&nbsp;&nbsp;&nbsp;&nbsp;main() <br>&nbsp;&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;char str[20]="C Language",c; <br>&nbsp;&nbsp;&nbsp;&nbsp;int i=0; <br>&nbsp;&nbsp;&nbsp;&nbsp;while((c=str[i])!='\0'){ <br>&nbsp;&nbsp;&nbsp;&nbsp;i++; <br>&nbsp;&nbsp;&nbsp;&nbsp;#if LETTER <br>&nbsp;&nbsp;&nbsp;&nbsp;if(c&gt;='a'&amp;&amp;c&lt;='z') c=c-32; <br>&nbsp;&nbsp;&nbsp;&nbsp;#else <br>&nbsp;&nbsp;&nbsp;&nbsp;if(c&gt;='A'&amp;&amp;c&lt;='Z') c=c+32; <br>&nbsp;&nbsp;&nbsp;&nbsp;#endif <br>&nbsp;&nbsp;&nbsp;&nbsp;printf("%c",c); <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;运行结果为：C LANGUAGE <br>&nbsp;&nbsp;&nbsp;&nbsp;现在先定义LETTER为1，这样在预处理条件编译命令时，由于LETTER为真（非零），则对第一个if语句进行编译，运行时使小写字母变大写。如果将程序第一行改为： <br>&nbsp;&nbsp;&nbsp;&nbsp;#define LETTER 0 <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;则在预处理时，对第二个if语句进行编译处理，使大写字母变成小写字母（大写字母与相应的小写字母的ASCII代码差32）。此时运行情况为： <br>&nbsp;&nbsp;&nbsp;&nbsp;c language <br>&nbsp;&nbsp;&nbsp;&nbsp;有人会问：不用条件编译命令而直接用if语句也能达到要求，用条件编译命令有什么好处呢？的确，此问题完全可以不用条件编译处理，但那样做目标程序长（因为所有语句都编译），而采用条件编译，可以减少被编译的语句，从而减少目标的长度。当条件编译段比较多时，目标程序长度可以大大减少。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52256.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 16:39 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52256.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>windows进程中的内存结构</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52237.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 03:39:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52237.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52237.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52237.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52237.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52237.html</trackback:ping><description><![CDATA[在阅读本文之前，如果你连堆栈是什么多不知道的话，请先阅读文章后面的基础知识。 <br>接触过编程的人都知道，高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢？程序又是如何使用这些变量的呢？下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明，默认都使用VC编译的release版。 <br>首先，来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local)，静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码： <br>#include &lt;stdio.h&gt; <br>int g1=0, g2=0, g3=0; <br>int main() <br>{ <br>static int s1=0, s2=0, s3=0; <br>int v1=0, v2=0, v3=0; <br>//打印出各个变量的内存地址 <br>printf("0x%08x\n",&amp;v1); //打印各本地变量的内存地址 <br>printf("0x%08x\n",&amp;v2); <br>printf("0x%08x\n\n",&amp;v3); <br>printf("0x%08x\n",&amp;g1); //打印各全局变量的内存地址 <br>printf("0x%08x\n",&amp;g2); <br>printf("0x%08x\n\n",&amp;g3); <br>printf("0x%08x\n",&amp;s1); //打印各静态变量的内存地址 <br>printf("0x%08x\n",&amp;s2); <br>printf("0x%08x\n\n",&amp;s3); <br>return 0; <br>} <br>编译后的执行结果是： <br>0x0012ff78 <br>0x0012ff7c <br>0x0012ff80 <br>0x004068d0 <br>0x004068d4 <br>0x004068d8 <br>0x004068dc <br>0x004068e0 <br>0x004068e4 <br>输 出的结果就是变量的内存地址。其中v1,v2,v3是本地变量，g1,g2,g3是全局变量，s1,s2,s3是静态变量。你可以看到这些变量在内存是连 续分布的，但是本地变量和全局变量分配的内存地址差了十万八千里，而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不 同类型的内存区域中的结果。对于一个进程的内存空间而言，可以在逻辑上分成3个部份：代码区，静态数据区和动态数据区。动态数据区一般就是&#8220;堆栈&#8221;。&#8220;栈 (stack)&#8221;和&#8220;堆(heap)&#8221;是两种不同的动态数据区，栈是一种线性结构，堆是一种链式结构。进程的每个线程都有私有的&#8220;栈&#8221;，所以每个线程虽然 代码一样，但本地变量的数据都是互不干扰。一个堆栈可以通过&#8220;基地址&#8221;和&#8220;栈顶&#8221;地址来描述。全局变量和静态变量分配在静态数据区，本地变量分配在动态数 据区，即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。 <br><br>├———————┤低端内存区域 <br>│ &#8230;&#8230; │ <br>├———————┤ <br>│ 动态数据区 │ <br>├———————┤ <br>│ &#8230;&#8230; │ <br>├———————┤ <br>│ 代码区 │ <br>├———————┤ <br>│ 静态数据区 │ <br>├———————┤ <br>│ &#8230;&#8230; │ <br>├———————┤高端内存区域 <br><br>堆 栈是一个先进后出的数据结构，栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程，以便对堆栈在程序中的作用有更深入的了解。不同的语言 有不同的函数调用规定，这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的，前者由被调函 数调整堆栈，后者由调用者调整堆栈。两者通过&#8220;__stdcall&#8221;和&#8220;__cdecl&#8221;前缀区分。先看下面这段代码： <br>#include &lt;stdio.h&gt; <br>void __stdcall func(int param1,int param2,int param3) <br>{ <br>int var1=param1; <br>int var2=param2; <br>int var3=param3; <br>printf("0x%08x\n",?m1); //打印出各个变量的内存地址 <br>printf("0x%08x\n",?m2); <br>printf("0x%08x\n\n",?m3); <br>printf("0x%08x\n",&amp;var1); <br>printf("0x%08x\n",&amp;var2); <br>printf("0x%08x\n\n",&amp;var3); <br>return; <br>} <br>int main() <br>{ <br>func(1,2,3); <br>return 0; <br>} <br>编译后的执行结果是： <br>0x0012ff78 <br>0x0012ff7c <br>0x0012ff80 <br>0x0012ff68 <br>0x0012ff6c <br>0x0012ff70 <br><br>├———————┤&lt;—函数执行时的栈顶（ESP）、低端内存区域 <br>│ &#8230;&#8230; │ <br>├———————┤ <br>│ var 1 │ <br>├———————┤ <br>│ var 2 │ <br>├———————┤ <br>│ var 3 │ <br>├———————┤ <br>│ RET │ <br>├———————┤&lt;—&#8220;__cdecl&#8221;函数返回后的栈顶（ESP） <br>│ parameter 1 │ <br>├———————┤ <br>│ parameter 2 │ <br>├———————┤ <br>│ parameter 3 │ <br>├———————┤&lt;—&#8220;__stdcall&#8221;函数返回后的栈顶（ESP） <br>│ &#8230;&#8230; │ <br>├———————┤&lt;—栈底（基地址 EBP）、高端内存区域 <br><br>上 图就是函数调用过程中堆栈的样子了。首先，三个参数以从又到左的次序压入堆栈，先压&#8220;param3&#8221;，再压&#8220;param2&#8221;，最后压入&#8220;param1&#8221;； 然后压入函数的返回地址(RET)，接着跳转到函数地址接着执行（这里要补充一点，介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后，继续压入 当前EBP，然后用当前ESP代替EBP。然而，有一篇介绍windows下函数调用的文章中说，在windows下的函数调用也有这一步骤，但根据我的 实际调试，并未发现这一步，这还可以从param3和var1之间只有4字节的间隙这点看出来）；第三步，将栈顶(ESP)减去一个数，为本地变量分配内 存空间，上例中是减去12字节(ESP=ESP-3*4，每个int变量占用4个字节)；接着就初始化本地变量的内存空间。由于&#8220;__stdcall&#8221;调 用由被调函数调整堆栈，所以在函数返回前要恢复堆栈，先回收本地变量占用的内存(ESP=ESP+3*4)，然后取出返回地址，填入EIP寄存器，回收先 前压入参数占用的内存(ESP=ESP+3*4)，继续执行调用者的代码。参见下列汇编代码： <br>;--------------func 函数的汇编代码------------------- <br>:00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间 <br>:00401003 8B442410 mov eax, dword ptr [esp+10] <br>:00401007 8B4C2414 mov ecx, dword ptr [esp+14] <br>:0040100B 8B542418 mov edx, dword ptr [esp+18] <br>:0040100F 89442400 mov dword ptr [esp], eax <br>:00401013 8D442410 lea eax, dword ptr [esp+10] <br>:00401017 894C2404 mov dword ptr [esp+04], ecx <br>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;（省略若干代码） <br>:00401075 83C43C add esp, 0000003C ;恢复堆栈，回收本地变量的内存空间 <br>:00401078 C3 ret 000C ;函数返回，恢复参数占用的内存空间 <br>;如果是&#8220;__cdecl&#8221;的话，这里是&#8220;ret&#8221;，堆栈将由调用者恢复 <br>;-------------------函数结束------------------------- <br><br>;--------------主程序调用func函数的代码-------------- <br>:00401080 6A03 push 00000003 //压入参数param3 <br>:00401082 6A02 push 00000002 //压入参数param2 <br>:00401084 6A01 push 00000001 //压入参数param1 <br>:00401086 E875FFFFFF call 00401000 //调用func函数 <br>;如果是&#8220;__cdecl&#8221;的话，将在这里恢复堆栈，&#8220;add esp, 0000000C&#8221; <br>聪明的读者看到这里，差不多就明白缓冲溢出的原理了。先来看下面的代码： <br>#include &lt;stdio.h&gt; <br>#include &lt;string.h&gt; <br>void __stdcall func() <br>{ <br>char lpBuff[8]="\0"; <br>strcat(lpBuff,"AAAAAAAAAAA"); <br>return; <br>} <br>int main() <br>{ <br>func(); <br>return 0; <br>} <br>编 译后执行一下回怎么样？哈，&#8220;"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。&#8221;，&#8220;非法操作&#8221;喽！ "41"就是"A"的16进制的ASCII码了，那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节，算进结尾的\0，那 strcat最多只能写入7个"A"，但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图，多出来的4个字节正好覆盖了RET的所在的内存空 间，导致函数返回到一个错误的内存地址，执行了错误的指令。如果能精心构造这个字符串，使它分成三部分，前一部份仅仅是填充的无意义数据以达到溢出的目 的，接着是一个覆盖RET的数据，紧接着是一段shellcode，那只要着个RET地址能指向这段shellcode的第一个指令，那函数返回时就能执 行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置，那么要构造这个RET是十分困难的。一般 都在RET和shellcode之间填充大量的NOP指令，使得exploit有更强的通用性。 <br><br>├———————┤&lt;—低端内存区域 <br>│ &#8230;&#8230; │ <br>├———————┤&lt;—由exploit填入数据的开始 <br>│ │ <br>│ buffer │&lt;—填入无用的数据 <br>│ │ <br>├———————┤ <br>│ RET │&lt;—指向shellcode，或NOP指令的范围 <br>├———————┤ <br>│ NOP │ <br>│ &#8230;&#8230; │&lt;—填入的NOP指令，是RET可指向的范围 <br>│ NOP │ <br>├———————┤ <br>│ │ <br>│ shellcode │ <br>│ │ <br>├———————┤&lt;—由exploit填入数据的结束 <br>│ &#8230;&#8230; │ <br>├———————┤&lt;—高端内存区域 <br><br>windows下的动态数据除了可存放在栈中，还可以存放在堆中。了解C++的朋友都知道，C++可以使用new关键字来动态分配内存。来看下面的C++代码： <br>#include &lt;stdio.h&gt; <br>#include &lt;iostream.h&gt; <br>#include &lt;windows.h&gt; <br>void func() <br>{ <br>char *buffer=new char[128]; <br>char bufflocal[128]; <br>static char buffstatic[128]; <br>printf("0x%08x\n",buffer); //打印堆中变量的内存地址 <br>printf("0x%08x\n",bufflocal); //打印本地变量的内存地址 <br>printf("0x%08x\n",buffstatic); //打印静态变量的内存地址 <br>} <br>void main() <br>{ <br>func(); <br>return; <br>} <br>程序执行结果为： <br>0x004107d0 <br>0x0012ff04 <br>0x004068c0 <br>可以发现用new关键字分配的内存即不在栈中，也不在静态数据区。VC编译器是通过windows下的&#8220;堆(heap)&#8221;来实现new关键字的内存动态分配。在讲&#8220;堆&#8221;之前，先来了解一下和&#8220;堆&#8221;有关的几个API函数： <br>HeapAlloc 在堆中申请内存空间 <br>HeapCreate 创建一个新的堆对象 <br>HeapDestroy 销毁一个堆对象 <br>HeapFree 释放申请的内存 <br>HeapWalk 枚举堆对象的所有内存块 <br>GetProcessHeap 取得进程的默认堆对象 <br>GetProcessHeaps 取得进程所有的堆对象 <br>LocalAlloc <br>GlobalAlloc <br>当进程初始化时，系统会自动为进程创建一个默认堆，这个堆默认所占内存的大小为1M。堆对象由系统进行管理，它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间： <br>HANDLE hHeap=GetProcessHeap(); <br>char *buff=HeapAlloc(hHeap,0,8); <br>其中hHeap是堆对象的句柄，buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢？它的值有什么意义吗？看看下面这段代码吧： <br>#pragma comment(linker,"/entry:main") //定义程序的入口 <br>#include &lt;windows.h&gt; <br>_CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf <br>/*--------------------------------------------------------------------------- <br>写到这里，我们顺便来复习一下前面所讲的知识： <br>(*注)printf函数是C语言的标准函数库中函数，VC的标准函数库由msvcrt.dll模块实现。 <br>由 函数定义可见，printf的参数个数是可变的，函数内部无法预先知道调用者压入的参数个数，函数只能通过分析第一个参数字符串的格式来获得压入参数的信 息，由于这里参数的个数是动态的，所以必须由调用者来平衡堆栈，这里便使用了__cdecl调用规则。BTW，Windows系统的API函数基本上是 __stdcall调用形式，只有一个API例外，那就是wsprintf，它使用__cdecl调用规则，同printf函数一样，这是由于它的参数个 数是可变的缘故。 <br>---------------------------------------------------------------------------*/ <br>void main() <br>{ <br>HANDLE hHeap=GetProcessHeap(); <br>char *buff=HeapAlloc(hHeap,0,0x10); <br>char *buff2=HeapAlloc(hHeap,0,0x10); <br>HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); <br>printf=(void *)GetProcAddress(hMsvcrt,"printf"); <br>printf("0x%08x\n",hHeap); <br>printf("0x%08x\n",buff); <br>printf("0x%08x\n\n",buff2); <br>} <br>执行结果为： <br>0x00130000 <br>0x00133100 <br>0x00133118 <br>hHeap 的值怎么和那个buff的值那么接近呢？其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构，这个 结构中存放着一些有关进程的重要信息，其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址，而偏移0x90处存放了指向 进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据，如windows 2000下的所有ANSI版本的函数都是 在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的，同一时刻只能有一个线程访问堆中的数据，当多个线程同时 有访问要求时，只能排队等待，这样便造成程序执行效率下降<br>最后来说说内存中的数据对齐。所位数据对齐，是指数据所在的内存地址必须是该 数据长度的整数倍，DWORD数据的内存起始地址能被4除尽，WORD数据的内存起始地址能被2除尽，x86 CPU能直接访问对齐的数据，当他试图访问 一个未对齐的数据时，会在内部进行一系列的调整，这些调整对于程序来说是透明的，但是会降低运行速度，所以编译器在编译程序时会尽量保证数据对齐。同样一 段代码，我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果： <br>#include &lt;stdio.h&gt; <br>int main() <br>{ <br>int a; <br>char b; <br>int c; <br>printf("0x%08x\n",&amp;a); <br>printf("0x%08x\n",&amp;b); <br>printf("0x%08x\n",&amp;c); <br>return 0; <br>} <br>这是用VC编译后的执行结果： <br>0x0012ff7c <br>0x0012ff7b <br>0x0012ff80 <br>变量在内存中的顺序：b(1字节)-a(4字节)-c(4字节)。 <br>这是用Dev-C++编译后的执行结果： <br>0x0022ff7c <br>0x0022ff7b <br>0x0022ff74 <br>变量在内存中的顺序：c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。 <br>这是用lcc编译后的执行结果： <br>0x0012ff6c <br>0x0012ff6b <br>0x0012ff64 <br>变量在内存中的顺序：同上。 <br>三个编译器都做到了数据对齐，但是后两个编译器显然没VC&#8220;聪明&#8221;，让一个char占了4字节，浪费内存哦。 <br><br>基础知识： <br>堆 栈是一种简单的数据结构，是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶，另一端称为栈底，对堆栈的插入和删除操作被称 为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中，POP指令实现出栈操作，PUSH指令实现入栈操作。CPU的ESP寄存器存放 当前线程的栈顶指针，EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址，当CPU执行完当前的指令后，从 EIP寄存器中读取下一条指令的内存地址，然后继续执行。 <br><br>参考：《Windows下的HEAP溢出及其利用》by: isno <br>《windows核心编程》by: Jeffrey Richter <br><br><br><br><br>摘要： 讨论常见的堆性能问题以及如何防范它们。（共 9 页）<br>前言<br>您 是否是动态分配的 C/C++ 对象忠实且幸运的用户？您是否在模块间的往返通信中频繁地使用了&#8220;自动化&#8221;？您的程序是否因堆分配而运行起来很慢？不仅仅 您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说，&#8220;我的代码真正好，只是堆太慢&#8221;。那只是部分正确。更深入理解堆及其用法、以及会发生什 么问题，是很有用的。<br>什么是堆？<br>（如果您已经知道什么是堆，可以跳到&#8220;什么是常见的堆性能问题？&#8221;部分）<br>在程序中，使用堆来动态分配和释放对象。在下列情况下，调用堆操作： <br>事先不知道程序所需对象的数量和大小。<br><br>对象太大而不适合堆栈分配程序。<br>堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。<br><a href="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" target=_blank><wbr><a href="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" target=_blank><img style="VERTICAL-ALIGN: baseline! important" src="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" onload=picsize(this,670,999) border=0></a><wbr></a><wbr><br>GlobalAlloc/GlobalFree：Microsoft Win32 堆调用，这些调用直接与每个进程的默认堆进行对话。<br>LocalAlloc/LocalFree：Win32 堆调用（为了与 Microsoft Windows NT 兼容），这些调用直接与每个进程的默认堆进行对话。<br>COM 的 IMalloc 分配程序（或 CoTaskMemAlloc / CoTaskMemFree）：函数使用每个进程的默认堆。自动化程序使用&#8220;组件对象模型 (COM)&#8221;的分配程序，而申请的程序使用每个进程堆。<br>C/C ++ 运行时 (CRT) 分配程序：提供了 malloc() 和 free() 以及 new 和 delete 操作符。如&nbsp;&nbsp;Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆，驻留在&nbsp;&nbsp;Win32 堆的顶部。<br>Windows NT 中，Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。<br>Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。<br>在图表的底部是&#8220;虚拟内存分配程序&#8221;，操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。<br>分配和释放块不就那么简单吗？为何花费这么长时间？<br>堆实现的注意事项<br>传 统上，操作系统和运行时库是与堆的实现共存的。在一个进程的开始，操作系统创建一个默认堆，叫做&#8220;进程堆&#8221;。如果没有其他堆可使用，则块的分配使用&#8220;进程 堆&#8221;。语言运行时也能在进程内创建单独的堆。（例如，C 运行时创建它自己的堆。）除这些专用的堆外，应用程序或许多已载入的动态链接库 (DLL) 之 一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数（英文）的详尽指导，请参见 MSDN。<br>当应用程序或 DLL 创建私有堆时，这些堆存在于进程空间，并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。（不能从一个堆分配而在另一个堆释放。）<br>在所有虚拟内存系统中，堆驻留在操作系统的&#8220;虚拟内存管理器&#8221;的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下，这些堆是操作系统堆中的层，而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆，而使用虚拟内存函数更利于堆的分配和块的使用。<br>典 型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用，堆尝试从前端列表找到一个自由块。如果失败，堆被迫从后端 （保留和提交虚拟内存）分配一个大块来满足请求。通用的实现有每块分配的开销，这将耗费执行周期，也减少了可使用的存储空间。<br>Knowledge Base&nbsp;&nbsp;文章 Q10758，&#8220;用 calloc() 和 malloc() 管理内存&#8221; （搜索文章编号）, 包含了有关这些主题的更多背景知识。另外，有关堆 实现和设计的详细讨论也可在下列著作中找到：&#8220;Dynamic Storage Allocation:&nbsp;&nbsp;A Survey and Critical Review&#8221;，作者 Paul R. Wilson、Mark S. Johnstone、 Michael Neely 和 David Boles； &#8220;International Workshop on Memory Management&#8221;, 作者 Kinross, Scotland, UK,&nbsp;&nbsp;1995 年 9 月(<wbr><a href="http://club.5ivb.net/pic/url.gif" target=_blank><img style="VERTICAL-ALIGN: baseline! important" src="http://club.5ivb.net/pic/url.gif" onload=picsize(this,670,999) border=0></a><wbr><a href="http://www.cs.utexas.edu/users/oops/papers.html" target=_blank><font style="LINE-HEIGHT: 1.3em" color=#000000><u><wbr>http://www.cs.utexas.edu/users/oops/papers.html</u><wbr></font><wbr style="LINE-HEIGHT: 1.3em"></a><wbr>)（英文）。<br>Windows NT&nbsp;&nbsp;的实现（Windows NT 版本 4.0 和更新版本） 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个&#8220;大 块&#8221;列表。&#8220;大块&#8221;列表（空闲列表[0]） 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下，&#8220;进程堆&#8221;执行收 集操作。（收集是将相邻空闲块合并成一个大块的操作。）收集耗费了额外的周期，但减少了堆块的内部碎片。<br>单一全局锁保护堆，防止多线程式的使用。（请参见&#8220;Server Performance and Scalability Killers&#8221;中的第一个注意事项, George Reilly 所著，在 &#8220;MSDN Online Web Workshop&#8221;上（站点：<wbr><a href="http://club.5ivb.net/pic/url.gif" target=_blank><img style="VERTICAL-ALIGN: baseline! important" src="http://club.5ivb.net/pic/url.gif" onload=picsize(this,670,999) border=0></a><wbr><a href="http://msdn.microsoft.com/workshop/server/iis/tencom.asp" target=_blank><font style="LINE-HEIGHT: 1.3em" color=#000000><u><wbr>http://msdn.microsoft.com/workshop/server/iis/tencom.asp</u><wbr></font><wbr style="LINE-HEIGHT: 1.3em"></a><wbr>（英文）。）单一全局锁本质上是用来保护堆数据结构，防止跨多线程的随机存取。若堆操作太频繁，单一全局锁会对性能有不利的影响。<br>什么是常见的堆性能问题？<br>以下是您使用堆时会遇到的最常见问题： <br>分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块，所以运行时分配程序代码会耗费周期寻找较大的空闲块，或从后端分配程序分配新块。<br><br>释放操作造成的速度减慢。释放操作耗费较多周期，主要是启用了收集操作。收集期间，每个释放操作&#8220;查找&#8221;它的相邻块，取出它们并构造成较大块，然后再把此较大块插入空闲列表。在查找期间，内存可能会随机碰到，从而导致高速缓存不能命中，性能降低。<br><br>堆 竞争造成的速度减慢。当两个或多个线程同时访问数据，而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦；这也是目前多处理 器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行（或运行于多处理器系统上）时将导致速度减慢。单一锁定的使用—常用的解 决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。 <br>竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的，但开销更大的是数据从处理器高速缓存中丢失，以及后来线程复活时的数据重建。<br>堆 破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块，以及块的越界重写等明显问题。 （破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节，请参见 Microsoft Visual C++(R) 调试文档 。）<br><br>频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配，随重分配增长和释放。不要这样做，如果可能，尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。<br>竞争是在分配和释放操作中导致速度减慢的问题。理想情况下，希望使用没有竞争和快速分配/释放的堆。可惜，现在还没有这样的通用堆，也许将来会有。<br>在所有的服务器系统中（如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他）, 堆锁定实在是个大瓶颈。处理器数越多，竞争就越会恶化。<br>尽量减少堆的使用<br>现在您明白使用堆时存在的问题了，难道您不想拥有能解决这些问题的超级魔棒吗？我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略，情况将会大大好转。调整使用堆的方法，减少对堆的操作是提高性能的良方。<br>如何减少使用堆操作？通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例：<br>struct ObjectA {<br>&nbsp;&nbsp; // objectA 的数据 <br>}<br>struct ObjectB {<br>&nbsp;&nbsp; // objectB 的数据 <br>}<br>// 同时使用 objectA 和 objectB<br>//<br>// 使用指针 <br>//<br>struct ObjectB {<br>&nbsp;&nbsp; struct ObjectA * pObjA;<br>&nbsp;&nbsp; // objectB 的数据 <br>}<br>//<br>// 使用嵌入<br>//<br>struct ObjectB {<br>&nbsp;&nbsp; struct ObjectA pObjA;<br>&nbsp;&nbsp; // objectB 的数据 <br>}<br>//<br>// 集合 &#8211; 在另一对象内使用 objectA 和 objectB<br>//<br>struct ObjectX {<br>&nbsp;&nbsp; struct ObjectA&nbsp;&nbsp;objA;<br>&nbsp;&nbsp; struct ObjectB&nbsp;&nbsp;objB;<br>}<br>避免使用指针关联两个数据结构。如果使用指针关联两个数据结构，前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。<br><br>把带指针的子对象嵌入父对象。当对象中有指针时，则意味着对象中有动态元素（百分之八十）和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。<br><br>合并小对象形成大对象（聚合）。聚合减少分配和释放的块的数量。如果有几个开发者，各自开发设计的不同部分，则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。<br><br>内 联缓冲区能够满足百分之八十的需要（aka 80-20 规则）。个别情况下，需要内存缓冲区来保存字符串/二进制数据，但事先不知道总字节数。估计并内 联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十，可以分配一个新的缓冲区和指向这个缓冲区的指针。这样，就减少分配和释放调用并增加数据的 位置空间，从根本上提高代码的性能。<br><br>在块中分配对象（块化）。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪， 例如对一个 {名称，值} 对的列表，有两种选择：选择一是为每一个&#8220;名称-值&#8221;对分配一个节点；选择二是分配一个能容纳（如五个）&#8220;名称-值&#8221;对的结 构。例如，一般情况下，如果存储四对，就可减少节点的数量，如果需要额外的空间数量，则使用附加的链表指针。 <br>块化是友好的处理器高速缓存，特别是对于 L1-高速缓存，因为它提供了增加的位置 —不用说对于块分配，很多数据块会在同一个虚拟页中。<br>正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序，该分配程序从后端（Win32 堆）分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。<br>使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面，代码会有点特殊，但如果经过深思熟虑，代码还是很容易管理的。<br>其他提高性能的技术<br>下面是一些提高速度的技术： <br>使用 Windows NT5 堆 <br>由于几个同事的努力和辛勤工作，1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进：<br>改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构，防止多线程式的使用。但不幸的是，在高通信量的情况下，堆仍受困于全局锁，导致高竞争和低性能。Windows 2000 中，锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。<br><br>使 用 &#8220;Lookaside&#8221;列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节（以 8-字节递增）的快速高速缓存。快速高速缓存 最初保护在全局锁内。现在，使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定，而是使用 64 位的互锁操作，因此提 高了性能。<br><br>内部数据结构算法也得到改进。<br>这些改进避免了对分配高速缓存的需求，但不排除其他的优化。使用&nbsp;&nbsp;Windows NT5 堆评估您的代码；它对小于 1,024 字节 (1 KB) 的块（来自前端分配程序的块）是最佳的。GlobalAlloc () 和 LocalAlloc() 建立在同一堆上，是存取每个进程堆的通用机制。如果希望获得高的局部性能，则使用 Heap(R) API 来存取 每个进程堆，或为分配操作创建自己的堆。如果需要对大块操作，也可以直接使用 VirtualAlloc() / VirtualFree() 操作。<br>上 述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后，堆锁的竞争率显著降低。这使所有&nbsp;&nbsp;Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部，但它使用自己的小块堆，因而不能从 Windows NT 改进中受益。 （Visual C++ 版本 6.0 也有改进的堆分配程序。）<br>使用分配高速缓存 <br>分配高速缓存允许高速缓存分配的块，以便将来重用。这能够减少对进程堆（或全局堆）的分配/释放调用的次数，也允许最大限度的重用曾经分配的块。另外，分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。<br>典 型地，自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设 计成一套固定大小（如 32 字节、64 字节、128 字节等）。这一个很好的策略，但这种自定义堆分配程序丢失与分配和释放的对象相关的&#8220;语义信 息&#8221;。 <br>与自定义堆分配程序相反，&#8220;分配高速缓存&#8221;作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外，它们还能够保 留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化，这些参数表示并发级别、对象大小和保持在空闲列表中 的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池（不超过指定的阀值）并使用私有保护锁。合在一起，分配高速缓存和私有锁减少了与主系 统堆的通信量，因而提供了增加的并发、最大限度的重用和较高的可伸缩性。<br>需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动，将释放分配对象的池，从而提高性能。<br>可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一，这种关系可以用来减少内存分配。<br>分配高速缓存也起到了调试助手的作用，帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名，甚至能够找到确切的失败的调用者。<br>MP 堆 <br>MP&nbsp;&nbsp;堆是对多处理器友好的分布式分配的程序包，在 Win32 SDK（Windows NT 4.0 和更新版本）中可以得到。最初由 JVert 实现， 此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆，并试图将分配调用分布到不同堆，以减少在所有单一锁上的竞争。<br>本 程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是，它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果 使用这个 SDK 创建可重用组件，您将大大受益。但是，如果在每个 DLL 中建立这个 SDK 库，将增加工作设置。<br>重新思考算法和数据结构 <br>要 在多处理器机器上伸缩，则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问，&#8220;我能用不同的数据结构完成此工作吗？&#8221;例 如，如果在应用程序初始化时加载了只读项的列表，这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎 片，从而增强性能。<br>减少需要的小对象的数量减少堆分配程序的负载。例如，我们在服务器的关键处理路径上使用五个不同的对象，每个对象单独分配和释放。一起高速缓存这些对象，把堆调用从五个减少到一个，显著减少了堆的负载，特别当每秒钟处理 1,000 个以上的请求时。<br>如果大量使用&#8220;Automation&#8221;结构，请考虑从主线代码中删除&#8220;Automation BSTR&#8221;，或至少避免重复的 BSTR 操作。（BSTR 连接导致过多的重分配和分配/释放操作。）<br>摘要<br>对所有平台往往都存在堆实现，因此有巨大的开销。每个单独代码都有特定的要求，但设计能采用本文讨论的基本理论来减少堆之间的相互作用。 <br>评价您的代码中堆的使用。<br><br>改进您的代码，以使用较少的堆调用：分析关键路径和固定数据结构。<br><br>在实现自定义的包装程序之前使用量化堆调用成本的方法。<br><br>如果对性能不满意，请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。<br><br>要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进，C 运行时堆调用的成本将减小。<br><br>操作系统（Windows NT 家族）正在不断改进堆。请随时关注和利用这些改进。<br>Murali Krishnan&nbsp;&nbsp;是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS，并成功发行 了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威 斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。工作之外，他喜欢阅读、打排球和家庭烹饪。<br><br><br><wbr><a href="http://club.5ivb.net/pic/url.gif" target=_blank><img style="VERTICAL-ALIGN: baseline! important" src="http://club.5ivb.net/pic/url.gif" onload=picsize(this,670,999) border=0></a><wbr><a href="http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835" target=_blank><font style="LINE-HEIGHT: 1.3em" color=#000000><u><wbr>http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835</u><wbr></font><wbr style="LINE-HEIGHT: 1.3em"></a><wbr><br>我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中，如下&nbsp;&nbsp;<br>CObject&nbsp;&nbsp;object;&nbsp;&nbsp;<br>还有一种是在堆(heap)中&nbsp;&nbsp;如下&nbsp;&nbsp;<br>CObject*&nbsp;&nbsp;pobject=new&nbsp;&nbsp;CObject();&nbsp;&nbsp;<br><br>请问&nbsp;&nbsp;<br>（1）这两种方式有什么区别？&nbsp;&nbsp;<br>（2）堆栈与堆有什么区别？？&nbsp;&nbsp;<br><br><br>---------------------------------------------------------------&nbsp;&nbsp;<br><br>1)&nbsp;&nbsp;about&nbsp;&nbsp;stack,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;automatically,&nbsp;&nbsp;and&nbsp;&nbsp;to&nbsp;&nbsp;the <br>heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;with&nbsp;&nbsp;new&nbsp;&nbsp;or&nbsp;&nbsp;malloc&nbsp;&nbsp;manually.&nbsp;&nbsp;<br>2)&nbsp;&nbsp;when&nbsp;&nbsp;function&nbsp;&nbsp;ends,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;automatically&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;of&nbsp;&nbsp;stack,&nbsp;&nbsp;but&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;<br>heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;manually&nbsp;&nbsp;with&nbsp;&nbsp;free&nbsp;&nbsp;or&nbsp;&nbsp;delete,&nbsp;&nbsp;else&nbsp;&nbsp;it&nbsp;&nbsp;will&nbsp;&nbsp;result&nbsp;&nbsp;in&nbsp;&nbsp;memory <br>leak.&nbsp;&nbsp;<br>3)栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&nbsp;&nbsp;<br>4）堆上分配的内存可以有我们自己决定，使用非常灵活。<img id=paperPicArea1 style="DISPLAY: none; POSITION: relative" src="http://imgcache.qq.com/ac/b.gif">
<img src ="http://www.cnitblog.com/ison/aggbug/52237.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 11:39 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52237.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ROM、RAM、DRAM、SRAM、FLASH的区别</title><link>http://www.cnitblog.com/ison/archive/2008/12/05/52238.html</link><dc:creator>ison</dc:creator><author>ison</author><pubDate>Fri, 05 Dec 2008 03:39:00 GMT</pubDate><guid>http://www.cnitblog.com/ison/archive/2008/12/05/52238.html</guid><wfw:comment>http://www.cnitblog.com/ison/comments/52238.html</wfw:comment><comments>http://www.cnitblog.com/ison/archive/2008/12/05/52238.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ison/comments/commentRss/52238.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ison/services/trackbacks/52238.html</trackback:ping><description><![CDATA[&nbsp;<strong><wbr>ROM和RAM</strong><wbr>指的都是半导体存储器，ROM是Read Only Memory的缩写，RAM是Random Access Memory的缩写。ROM在系统停止供电的时候仍然可以保持数据，而RAM通常都是在掉电之后就丢失数据，典型的RAM就是计算机的内存。 <br>&nbsp;&nbsp; <strong><wbr>RAM</strong><wbr>有两大类，一种称为静态RAM（Static RAM/SRAM），SRAM速度非常快，是目前读写最快的存储设备了，但是它也非常昂贵，所以只在要求很苛刻的地方使用，譬如CPU的一级缓冲，二级缓冲。另一种称为动态RAM（Dynamic RAM/DRAM），DRAM保留数据的时间很短，速度也比SRAM慢，不过它还是比任何的ROM都要快，但从价格上来说DRAM相比SRAM要便宜很多，计算机内存就是DRAM的。 <br>&nbsp;&nbsp;<strong><wbr>&nbsp;&nbsp;DRAM</strong><wbr>分为很多种，常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等，这里介绍其中的一种DDR RAM。DDR RAM（Date-Rate RAM）也称作DDR SDRAM，这种改进型的RAM和SDRAM是基本一样的，不同之处在于它可以在一个时钟读写两次数据，这样就使得数据传输速度加倍了。这是目前电脑中用得最多的内存，而且它有着成本优势，事实上击败了Intel的另外一种内存标准－Rambus DRAM。在很多高端的显卡上，也配备了高速DDR RAM来提高带宽，这可以大幅度提高3D加速卡的像素渲染能力。 <br>&nbsp;&nbsp; <strong><wbr>ROM</strong><wbr>也有很多种，PROM是可编程的ROM，PROM和EPROM（可擦除可编程ROM）两者区别是，PROM是一次性的，也就是软件灌入后，就无法修改了，这种是早期的产品，现在已经不可能使用了，而EPROM是通过紫外光的照射擦出原先的程序，是一种通用的存储器。另外一种EEPROM是通过电子擦出，价格很高，写入时间很长，写入很慢。 <br>&nbsp;&nbsp;&nbsp;&nbsp;<strong><wbr>举个例子</strong><wbr>，手机软件一般放在EEPROM中，我们打电话，有些最后拨打的号码，暂时是存在SRAM中的，不是马上写入通过记录（通话记录保存在EEPROM中），因为当时有很重要工作（通话）要做，如果写入，漫长的等待是让用户忍无可忍的。 <br>&nbsp;&nbsp;&nbsp;&nbsp;<strong><wbr>FLASH</strong><wbr>存储器又称闪存，它结合了ROM和RAM的长处，不仅具备电子可擦出可编程（EEPROM）的性能，还不会断电丢失数据同时可以快速读取数据（NVRAM的优势），U盘和MP3里用的就是这种存储器。在过去的20年里，嵌入式系统一直使用ROM（EPROM）作为它们的存储设备，然而近年来Flash全面代替了ROM（EPROM）在嵌入式系统中的地位，用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用（U盘）。 <br>&nbsp;&nbsp;&nbsp;&nbsp;目前Flash主要有两种<strong><wbr>NOR Flash和NADN Flash</strong><wbr>。NOR Flash的读取和我们常见的SDRAM的读取是一样，用户可以直接运行装载在NOR FLASH里面的代码，这样可以减少SRAM的容量从而节约了成本。NAND Flash没有采取内存的随机读取技术，它的读取是以一次读取一快的形式来进行的，通常是一次读取512个字节，采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码，因此好多使用NAND Flash的开发板除了使用NAND Flah以外，还作上了一块小的NOR Flash来运行启动代码。 <br>&nbsp;&nbsp;&nbsp;&nbsp;<strong><wbr>一般</strong><wbr>小容量的用NOR Flash，因为其读取速度快，多用来存储操作系统等重要信息，而大容量的用NAND FLASH，最常见的NAND FLASH应用是嵌入式系统采用的DOC（Disk On Chip）和我们通常用的&#8220;闪盘&#8221;，可以在线擦除。目前市面上的FLASH 主要来自Intel，AMD，Fujitsu和Toshiba，而生产NAND Flash的主要厂家有Samsung和Toshiba。
<img src ="http://www.cnitblog.com/ison/aggbug/52238.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ison/" target="_blank">ison</a> 2008-12-05 11:39 <a href="http://www.cnitblog.com/ison/archive/2008/12/05/52238.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>