<?xml version="1.0" encoding="gb2312"?>

<!-- RSS generated by oioj.net on 4/16/2004 ; 感谢LeXRus提供 RSS 2.0 文档; 此文件可自由使用，但请保留此行信息 --> 
<!-- Source download URL: http://blogger.org.cn/blog/rss2.asp       -->
<rss version="2.0">

<channel>
<title>enorm的博客</title>
<link>http://blogger.org.cn/blog/blog.asp?name=enorm</link>
<description>enorm的博客</description>
<copyright>blogger.org.cn</copyright>
<generator>W3CHINA Blog</generator>
<webMaster>webmaster@blogger.org.cn</webMaster>
<item>
<title><![CDATA[[收藏]深入剖析：C++“多态性”在编译器中的实现(ZT) -- 作者：enorm]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=enorm&amp;id=10607</link>
<author>enorm</author>
<pubDate>2005/12/15 10:04:28</pubDate>
<description><![CDATA[本文转载自<a target="_blank" href="http://bbs.w3china.org/">W3CHINA.ORG讨论区</a>(BBS.W3CHINA.ORG)&nbsp;&nbsp;&nbsp;<a target="_blank" href="http://bbs.w3china.org/dispbbs.asp?boardid=61&amp;ID=25471&amp;replyID=16360&amp;skin=1">原文链接</a><br>作者：<a target="_blank" href="http://bbs.w3china.org/dispuser.asp?name=enorm">enorm</a><br>以下为原文：<br><br><b>深入剖析：C++“多态性”在编译器中的实现(ZT)</b><br><br>理论<p></p><p>如下内容，属个人的理解．与大家共享．错误之处，请指正</p><p>程序，就是通过CPU指令（CPU指令就是CPU能识别的二进制流，CPU通过解释指令，能发出各种电流脉冲，以达到控制其他电子电路的状态），对内存中数据资源的操作，也就是改变内存的二进制数，也是改变高低电平。</p><p>内存中，都是二进制数据，哪是指令，哪是数据?</p><p>PC指令计数器所指向的内存单元，就是指令。</p><p>PC指向哪,哪就是指令,所以数据也是指令，指令也是数据，程序只要管好PC寄存器就可以了.</p><p>语言级上，程序，是由函数和数据组成，函数调用，实际上也是改变PC值，地址转移。</p><p>数据又分为两种:</p><p>一种就是在编译时,就分配地址空间.如全局数据</p><p>一种是在运行时,靠编译器所维护的栈顶指针,相对栈顶指针的偏移量,来分配地址空间.如局部数据.</p><p>(另一种就是在堆中动态分配的)</p><p>-------------------------------------------------------------------------------------------------------------------------------------</p><p>一个VC工程中，由许多H和CPP文件组成，</p><p>编译器，负责收集CPP文件（H文件被包含在CPP内）中出现的所有标识或符号，并负责形成逻辑、语法正确的二进制代码（obj文件）</p><p>连接器，负责确定、调整函数的相对地址，并保证在CPP中，每次函数调用（也就是地址转移），都是有效的，每个对内存数据的访问的地址，是有效的。</p><p>最终由连接器形成翻译成汇编代码或机器代码。（DLL和EXE可执行的文件）</p><p>为收集标识和符号，编译器维护一个符号映射表,有3栏（或3个字段）：<br>1。类型栏（变量类型或函数类型，在编译时，由编译器负责填写）<br>2。名字栏（变量名或函数名，在编译时，由编译器负责填写）<br>3。地址栏（是相对地址，成员变量类型的这一栏就放偏移值，加载后，由操作系统重新调整，也就是地址重定位，在连接时，由连接器负责填写）</p><p>-------------------------------------------------------------------------------------------------------------------------------------</p><p>实际应用</p><p>每声明定义一个变量或成员函数时，编译器就生成一个映射元素</p><p>如：</p><p>int a;</p><p>class A   </p><p>{</p><p>int m_a;</p><p>int m_b;</p><p>void fun1();</p><p>}</p><p>生成的两条映射表记录为：</p><p>名字栏  |    类型栏    |  地址栏()</p><p>   a          |     int           |   指向a的声明定义处，相对地址，加载后，由操作系统重新调整</p><p>  A::m_a |  int             |  偏移地址0,运行时，实际地址：this + 0</p><p>  A::m_b |  int             |   偏移地址4,运行时，实际地址：this + 4</p><p>  A::fun1 | A::fun1    |   指向A::fun1()定义处，相对地址，加栽后，由操作系统重新调整</p><p><br>有关编译器动态绑定技术，请看如下列：</p><p>class a<br>{</p><p>public:<br>  virtual fun1();</p><p> virtual fun6();</p><p>  void fun2();</p><p>/*</p><p>出现virtual 关键字，编译器为该类创建虚表</p><p>  索引           |         函数指针</p><p>  0                |     指向 a::fun1()定义处</p><p>  1               |     指向 a::fun6()定义处</p><p>出现成员函数声明，编译器填写映射表</p><p>名字栏       |    类型栏    |  地址栏</p><p>a::fun2()    |   a::fun2      | (由连接器填写)指向a::fun2()定义处</p><p>*/<br>}</p><p>//b继承自a</p><p>class b :public a</p><p>{</p><p>public:<br>  virtual fun1();</p><p>  void fun3();</p><p>/*</p><p>虚函数fun1()：</p><p>重新定义，创建虚表，并继承了父类的虚表项，</p><p>修改了虚表中第一项（“a::fun1()函数”）的指针，使指向自己定义b::fun1()函数</p><p>  索引           |         函数指针</p><p>  0                |     指向 b::fun1()定义处（重定义）</p><p> 1                |     指向 a::fun6()定义处(没有重定义)</p><p><br>出现成员函数fun3()声明，映射表中增加一栏：</p><p>名字栏       |    类型栏    |  地址栏</p><p>b::fun3()    |   b::fun3     | 指向b::fun3()定义处</p><p>从父类中继承的成员函数fun2()</p><p>映射表中增加一栏：记录从a继承的fun2()函数</p><p>名字栏       |    类型栏    |  地址栏</p><p>b::fun2()    |   a::fun2    | 指向父类a::fun2()定义处</p><p>*/<br>}</p><p>//c继承自a<br>class c :public a</p><p>{</p><p>public:<br>  virtual fun6();</p><p> void fun4();</p><p>/*</p><p>虚函数fun1()：</p><p>重新定义，创建虚表，并继承了父类的虚表项，</p><p>修改了虚表中第二项（“a::fun6()函数”）的指针，使指向自己定义c::fun6()函数</p><p>  索引           |         函数指针</p><p>  0                |     指向 a::fun1()定义处</p><p>  1                |     指向 c::fun6()定义处(改写)</p><p>出现 成员函数fun4()，映射表中增加一栏：</p><p>名字栏       |    类型栏    |  地址栏</p><p>c::fun4()    |   c::fun4     | 指向c::fun4()定义处</p><p>从父类中继承的成员函数fun2()</p><p>映射表中增加一栏：记录从a继承的fun2()函数</p><p>名字栏       |    类型栏    |  地址栏</p><p>c::fun2()    |   a::fun2    | 指向父类a::fun2()定义处</p><p>*/</p><p>}</p><p>调用<br>main()<br>{<br>  b     var1;<br>  c     var2;<br>  a*    p;<br> <br> p = &amp;var1;<br> </p><p>var1.fun2();</p><p>/*</p><p>var1.fun2()调用，静态绑定：</p><p>编译到此处时，编译器到映射表中找名字栏，找到b::fun2()名字（由于var1为b类型），其对应的类型栏为“函数类型，类型名为a::fun2（因为此函数由a类型定义）”，其地址栏的指针值为“指向a::fun2()定义处”，所以，此处函数调用，被编译器替换为“转向：地址栏的指针值”，实际上可理解为是修改指令记数器的值为“地址栏的指针值”</p><p>*/<br>*/</p><p>p-&gt;fun2();</p><p>/*</p><p>p-&gt;fun2()，静态绑定：这个容易理解，编译时，是找“a::fun2（）”名字（因为p是a类型）</p><p>*/</p><p>p-&gt;fun2()，静态绑定：这个容易理解，编译时，是找“a::fun2（）”名字（因为p是a类型）</p><p>*/</p><p>p-&gt;fun2()，静态绑定：这个容易理解，编译时，是找“a::fun2（）”名字（因为p是a类型）</p><p>*/</p><p>p-&gt;fun2()，静态绑定：这个容易理解，编译时，是找“a::fun2（）”名字（因为p是a类型）</p><p>*/</p><p>p-&gt;fun3();</p><p>/*</p><p> p-&gt;fun3()，这个调用，可能要发生编译错误，因为类型a没有声明和定义fun3()函数，找不到a::fun3()名字，只有b::fun3()名字</p><p>*/</p><p>p-&gt;fun1();</p><p>/*<br>p-&gt;fun1();fun1()是个虚函数，同理，编译到此处时，编译器是不是也到映射表中找“a::fun1()”名字呢？不是的。<br>因为，在映射表中，是找不到a::fun1()”这个名字的，因为，fun1()名字声明前有关键字"virtual"，<br>在类声明和定义时，编译器，为每个出现virtual关键字的类，维护一个全局数据结构“虚表”</p><p>虚表：</p><p>1。索引；2。函数指针（这值在子类重写虚函数后，发生相应变化）</p><p><br>所以上面调用，在编译器发现调用的是“虚函数”时，<br>编译器做了如下处理：</p><p>将p-&gt;fun1()  替换为：p-&gt;vptr[offset],</p><p>vptr名字为虚表指针：<br>由编译器维护（上面提到），每个含虚函数的类，其生成对象，在内存中，首先的四个字节就是vptr，这个值是静态的，同一个类型的所有对象的vptr值相同，指向同一个虚表；</p><p>offset值随虚函数声明次序而定，如果第一个声明，索引则为0，出现在虚表的第一项，第二个声明则为1，以次。。。（此时offset 为 0 即是：p-&gt;vptr[0]）</p><p>p为基类，可指向子类的任何对象</p><p>（附加：</p><p>    1。类型转换实际是：内存切割，管辖内存从大变小，从大变小，现实世界中，是允许，在C++中，也是允许的；编译器不允许基类对象向子类对象的转化，因为从小变大，会导致内存访问越界。</p><p>      2。指针的类型，实际上决定了通过该指针能访问的内存范围</p><p>    ），</p><p>所指向对象的类型不同，vptr不同<br> 所以，p-&gt;vptr[0],函数调用地址值在编译时，是不可能确定，主要因为<br>p指针所指向对象不能确定（p是指向b，还是指向c?还是其他。。，但offset是可以确定的）<br>从而，vptr值不能确定，<br>直到程序运行时，p-&gt;vptr[0]调用的函数地址，视p所指向对象类型而定<br>如果p指向b子类，则p指向内存中的首4字节的vptr指向b的虚表，虚表第一项的指针指向自己函数定义的地址处<br>如果p指向c子类，则。。。。<br>这种直到运行时，才能确定函数调用地址的方式，即为：“动态绑定”</p><p>*/</p><p>p-&gt;fun6();</p><p>/*</p><p>同上，由于b类未改写需函数fun6(),所以该处调用，实际调用a::fun6()，编译器做如下处理：</p><p>p-&gt;fun6() 变为  p-&gt;vptr[1],由于，fun6为第二声明，此时，offset为1</p><p>*/</p><p>p = &amp;var2;<br> p-&gt;fun1();//动态绑定，同上, 调用a::fun1()<br> p-&gt;fun2();//静态，同上</p><p>p-&gt;fun6();//调用c::fun6()<br> p-&gt;fun4();<br>//编译出错，因为fun4()非虚函数，编译器在映射表中找不到a::fun4()名字，<br>//除非强行转换：(（c*）p)-&gt;fun4()，}</p><p>虚表：</p><p>1。索引；2。函数指针（这值在子类重写虚函数后，发生相应变化）</p><p><br>所以上面调用，在编译器发现调用的是“虚函数”时，<br>编译器做了如下处理：</p><p>将p-&gt;fun1()  替换为：p-&gt;vptr[offset],</p><p>vptr名字为虚表指针：<br>由编译器维护（上面提到），每个含虚函数的类，其生成对象，在内存中，首先的四个字节就是vptr，这个值是静态的，同一个类型的所有对象的vptr值相同，指向同一个虚表；</p><p>offset值随虚函数声明次序而定，如果第一个声明，索引则为0，出现在虚表的第一项，第二个声明则为1，以次。。。（此时offset 为 0 即是：p-&gt;vptr[0]）</p><p>p为基类，可指向子类的任何对象</p><p>（附加：</p><p>    1。类型转换实际是：内存切割，管辖内存从大变小，从大变小，现实世界中，是允许，在C++中，也是允许的；编译器不允许基类对象向子类对象的转化，因为从小变大，会导致内存访问越界。</p><p>      2。指针的类型，实际上决定了通过该指针能访问的内存范围</p><p>    ），</p><p>所指向对象的类型不同，vptr不同<br> 所以，p-&gt;vptr[0],函数调用地址值在编译时，是不可能确定，主要因为<br>p指针所指向对象不能确定（p是指向b，还是指向c?还是其他。。，但offset是可以确定的）<br>从而，vptr值不能确定，<br>直到程序运行时，p-&gt;vptr[0]调用的函数地址，视p所指向对象类型而定<br>如果p指向b子类，则p指向内存中的首4字节的vptr指向b的虚表，虚表第一项的指针指向自己函数定义的地址处<br>如果p指向c子类，则。。。。<br>这种直到运行时，才能确定函数调用地址的方式，即为：“动态绑定”</p><p>*/</p><p>p-&gt;fun6();</p><p>/*</p><p>同上，由于b类未改写需函数fun6(),所以该处调用，实际调用a::fun6()，编译器做如下处理：</p><p>p-&gt;fun6() 变为  p-&gt;vptr[1],由于，fun6为第二声明，此时，offset为1</p><p>*/</p><p>p = &amp;var2;<br> p-&gt;fun1();//动态绑定，同上, 调用a::fun1()<br> p-&gt;fun2();//静态，同上</p><p>p-&gt;fun6();//调用c::fun6()<br> p-&gt;fun4();<br>//编译出错，因为fun4()非虚函数，编译器在映射表中找不到a::fun4()名字，<br>//除非强行转换：(（c*）p)-&gt;fun4()，}</p><p>虚表：</p><p>1。索引；2。函数指针（这值在子类重写虚函数后，发生相应变化）</p><p><br>所以上面调用，在编译器发现调用的是“虚函数”时，<br>编译器做了如下处理：</p><p>将p-&gt;fun1()  替换为：p-&gt;vptr[offset],</p><p>vptr名字为虚表指针：<br>由编译器维护（上面提到），每个含虚函数的类，其生成对象，在内存中，首先的四个字节就是vptr，这个值是静态的，同一个类型的所有对象的vptr值相同，指向同一个虚表；</p><p>offset值随虚函数声明次序而定，如果第一个声明，索引则为0，出现在虚表的第一项，第二个声明则为1，以次。。。（此时offset 为 0 即是：p-&gt;vptr[0]）</p><p>p为基类，可指向子类的任何对象</p><p>（附加：</p><p>    1。类型转换实际是：内存切割，管辖内存从大变小，从大变小，现实世界中，是允许，在C++中，也是允许的；编译器不允许基类对象向子类对象的转化，因为从小变大，会导致内存访问越界。</p><p>      2。指针的类型，实际上决定了通过该指针能访问的内存范围</p><p>    ），</p><p>所指向对象的类型不同，vptr不同<br> 所以，p-&gt;vptr[0],函数调用地址值在编译时，是不可能确定，主要因为<br>p指针所指向对象不能确定（p是指向b，还是指向c?还是其他。。，但offset是可以确定的）<br>从而，vptr值不能确定，<br>直到程序运行时，p-&gt;vptr[0]调用的函数地址，视p所指向对象类型而定<br>如果p指向b子类，则p指向内存中的首4字节的vptr指向b的虚表，虚表第一项的指针指向自己函数定义的地址处<br>如果p指向c子类，则。。。。<br>这种直到运行时，才能确定函数调用地址的方式，即为：“动态绑定”</p><p>*/</p><p>p-&gt;fun6();</p><p>/*</p><p>同上，由于b类未改写需函数fun6(),所以该处调用，实际调用a::fun6()，编译器做如下处理：</p><p>p-&gt;fun6() 变为  p-&gt;vptr[1],由于，fun6为第二声明，此时，offset为1</p><p>*/</p><p>p = &amp;var2;<br> p-&gt;fun1();//动态绑定，同上, 调用a::fun1()<br> p-&gt;fun2();//静态，同上</p><p>p-&gt;fun6();//调用c::fun6()<br> p-&gt;fun4();<br>//编译出错，因为fun4()非虚函数，编译器在映射表中找不到a::fun4()名字，<br>//除非强行转换：(（c*）p)-&gt;fun4()，}<br><br>&lt;完&gt;<br><a target="_blank" href="http://bbs.w3china.org/dispbbs.asp?boardid=61&amp;ID=25471&amp;replyID=16360&amp;skin=1">参与讨论本主题</a><br></p>]]></description>
</item><item>
<title><![CDATA[[收藏]bios内存分布图 -- 作者：enorm]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=enorm&amp;id=10573</link>
<author>enorm</author>
<pubDate>2005/12/13 21:58:26</pubDate>
<description><![CDATA[本文转载自<a target="_blank" href="http://bbs.w3china.org/">W3CHINA.ORG讨论区</a>(BBS.W3CHINA.ORG)&nbsp;&nbsp;&nbsp;<a target="_blank" href="http://bbs.w3china.org/dispbbs.asp?boardid=63&amp;ID=25336&amp;replyID=15996&amp;skin=1">原文链接</a><br>作者：<a target="_blank" href="http://bbs.w3china.org/dispuser.asp?name=enorm">enorm</a><br>以下为原文：<br><br><b>bios内存分布图</b><br><br><br><img src="http://bbs.xml.org.cn/images/files/jpg.gif" border="0">此主题相关图片如下：<br><a class="contentlink" href="http://bbs.xml.org.cn/uploadfile/2005121311185427778.jpg" target="_blank"><img src="http://bbs.xml.org.cn/uploadfile/2005121311185427778.jpg" alt="按此在新窗口浏览图片" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border="0"></a><br><br>&lt;完&gt;<br><a target="_blank" href="http://bbs.w3china.org/dispbbs.asp?boardid=63&amp;ID=25336&amp;replyID=15996&amp;skin=1">参与讨论本主题</a><br>]]></description>
</item><item>
<title><![CDATA[庆祝我的blog开张]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=enorm&amp;id=10331</link>
<author>enorm</author>
<pubDate>2005/12/2 18:31:56</pubDate>
<description><![CDATA[庆祝~~~~~~~~~~]]></description>
</item>
</channel>
</rss>