﻿<?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>C++博客-局部变量的作用域-随笔分类-开发语言</title><link>http://www.cppblog.com/localvar/category/15387.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 09 Nov 2010 02:04:33 GMT</lastBuildDate><pubDate>Tue, 09 Nov 2010 02:04:33 GMT</pubDate><ttl>60</ttl><item><title>C++/CLI的用途</title><link>http://www.cppblog.com/localvar/archive/2008/12/29/132770.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Mon, 29 Dec 2008 07:39:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2008/12/29/132770.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132770.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2008/12/29/132770.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132770.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132770.html</trackback:ping><description><![CDATA[<p>作为一个有着正常审美观的人，我简直无法忍受C++/CLI（以及managed c++）的丑陋。不过，近来发现，这个丑东西也还有点用，在把原生开发接口包装成托管开发接口时，比C#的互操作容易的多（互操作看了看，头大呀）。磕磕绊绊几天，终于把一个SDK开发包转换完成了。总结经验如下：</p>
<p>1. 对于clr中的引用类型，定义变量时要用个^符，如"String^ var1"、"array&lt;int&gt;^ var2"、"array&lt;String^&gt;^ strarr"等，值类型不用。一个类型是值类型还是引用类型，取决于定义时用的是value struct/class还是ref struct/class。</p>
<p>2. 定义枚举要用enum struct/class, 否则是个原生枚举，C#里不能用。可指定数值类型和flags属性，如下：<br>&nbsp;&nbsp;&nbsp; [FlagsAttribute]<br><span style="COLOR: #0000ff">&nbsp;&nbsp;&nbsp; public</span> <span style="COLOR: #0000ff">enum</span> <span style="COLOR: #0000ff">class</span> TestEnum : unsigned <span style="COLOR: #0000ff">int</span><br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flag1 = 0x00000001,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flag2 = 0x00000002,<br>&nbsp;&nbsp;&nbsp; };</p>
<p>3. 原生字符串转换为托管字符串时，用：<br><span style="COLOR: #0000ff">&nbsp;&nbsp;&nbsp; char</span>* s1 = "<span style="COLOR: #8b0000">native string1</span>";<br>&nbsp;&nbsp;&nbsp; wchar_t* s2 = L"<span style="COLOR: #8b0000">native string2</span>";<br>&nbsp;&nbsp;&nbsp; String^ str1 = gcnew String( s1 );<br>&nbsp;&nbsp;&nbsp; String^ str2 = gcnew String( s2 );<br>托管字符串转换为原生字符串时，用：<br>&nbsp;&nbsp;&nbsp; pin_ptr&lt;const wchar_t&gt; p = PtrToStringChars( str );<br>如果需要ansi字符集，可再对p进行一些常规字符集转换。</p>
<p>4. 指针、句柄等与0进行赋值比较等操作时用nullptr，而不是NULL或0，后者会导致装箱等操作，如：<br>&nbsp;&nbsp;&nbsp; HANDLE h = nullptr;<br>&nbsp;&nbsp;&nbsp; <span style="COLOR: #0000ff">if</span>( h == nullptr ){}</p>
<p>5. C#中定义函数参数时的ref关键字在C++/CLI中用%号对应，如：<br><span style="COLOR: #0000ff">&nbsp;&nbsp;&nbsp; void</span> foo( String^% refstr );<br>out关键字，需要用[System::Runtime::InteropServices::OutAttribute]声明一下。</p>
<p>6. 数组空间初始化，用()而不是[]，也就是说它是一个函数调用，如<br>&nbsp;&nbsp;&nbsp; array&lt;<span style="COLOR: #0000ff">int</span>&gt;^ arr = gcnew array&lt;<span style="COLOR: #0000ff">int</span>&gt;(100);<br>的作用是定义一个有100个元素的数组。</p>
<p>7. C++/CLI中很多地方不能用const、volatile等关键字，如果编译报错，就把它们去掉吧。</p>
<p>8. 尽量不要定义自己的DllMain，如果必须定义的话，DllMain中不要进行任何托管操作，否则极易导致死锁。可以"#pragma managed"编译指令，临时打开或关闭托管。</p>
<p>9. 暂时没有了，等想起来再补充。</p>
<img src="http://blog.vckbase.com/localvar/aggbug/36143.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132770.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2008-12-29 15:39 <a href="http://www.cppblog.com/localvar/archive/2008/12/29/132770.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多线程和函数里的静态变量</title><link>http://www.cppblog.com/localvar/archive/2008/05/29/132754.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Thu, 29 May 2008 01:33:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2008/05/29/132754.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132754.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2008/05/29/132754.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132754.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132754.html</trackback:ping><description><![CDATA[<div>试试下面这段代码的输出是什么?</div>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span> <span style="COLOR: #0000cc">&lt;</span>stdio<span style="COLOR: #0000cc">.</span>h<span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span> <span style="COLOR: #0000cc">&lt;</span>process<span style="COLOR: #0000cc">.</span>h<span style="COLOR: #0000cc">&gt;</span><br><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span> <span style="COLOR: #0000cc">&lt;</span>windows<span style="COLOR: #0000cc">.</span>h<span style="COLOR: #0000cc">&gt;</span><br><br><span style="COLOR: #0000ff">class</span> foo<br><span style="COLOR: #0000cc">{</span><br><span style="COLOR: #0000ff">public</span><span style="COLOR: #0000cc">:</span><br>&nbsp;&nbsp;&nbsp;&nbsp;foo<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span> <span style="COLOR: #ff00ff">"before sleep\n"</span> <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">Sleep</span><span style="COLOR: #0000cc">(</span> 1000 <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span> <span style="COLOR: #ff00ff">"after sleep\n"</span> <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">void</span> <span style="COLOR: #ff0000">test</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span> <span style="COLOR: #ff00ff">"in test\n"</span> <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><br>foo<span style="COLOR: #0000cc">*</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">static</span> foo a<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> <span style="COLOR: #0000cc">&amp;</span>a<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #0000ff">unsigned</span> __stdcall thread<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">void</span><span style="COLOR: #0000cc">*</span> <span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;foo<span style="COLOR: #0000cc">*</span> p <span style="COLOR: #0000cc">=</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;p<span style="COLOR: #0000cc">-</span><span style="COLOR: #0000cc">&gt;</span><span style="COLOR: #ff0000">test</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #0000ff">int</span> _cdecl main<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">int</span> argc<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000ff">char</span><span style="COLOR: #0000cc">*</span><span style="COLOR: #0000cc">*</span> argv <span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">for</span><span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">int</span> i <span style="COLOR: #0000cc">=</span> 0<span style="COLOR: #0000cc">;</span> i <span style="COLOR: #0000cc">&lt;</span> 10<span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span>i <span style="COLOR: #0000cc">)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">uintptr_t</span> t <span style="COLOR: #0000cc">=</span> _beginthreadex<span style="COLOR: #0000cc">(</span> <span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">,</span> 0<span style="COLOR: #0000cc">,</span> thread<span style="COLOR: #0000cc">,</span> <span style="COLOR: #ff0000">NULL</span><span style="COLOR: #0000cc">,</span> 0<span style="COLOR: #0000cc">,</span> <span style="COLOR: #ff0000">NULL</span> <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000cc">(</span>HANDLE<span style="COLOR: #0000cc">)</span>t <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff0000">Sleep</span><span style="COLOR: #0000cc">(</span> 5000 <span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<br>不知道C/C++标准有什么规定没有, 但粗看起来好像是编译器的问题呀。我用的是vc8，谁帮忙测测别的编译器。<br>根据星星的建议，把输出贴出来，如下：<br>before sleep<br>in test<br>in test<br>in test<br>in test<br>in test<br>in test<br>in test<br>in test<br>in test<br>after sleep<br>in test<br>这里的问题是至少有10个中的9个线程没有等对象初始化完成，就已经调用对象的方法了，这肯定是不对的。我大概看了一下反汇编的结果，实际上还可能出现构造函数被调用多次的情况。<br>要解决这个问题，在编译器的层次上要容易一点。如果是在用户程序的层次上，则麻烦的多，因为这类方法都会涉及到另一个静态变量的初始化。<img src="http://blog.vckbase.com/localvar/aggbug/33945.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132754.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2008-05-29 09:33 <a href="http://www.cppblog.com/localvar/archive/2008/05/29/132754.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>extern "C"</title><link>http://www.cppblog.com/localvar/archive/2008/03/10/132756.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Mon, 10 Mar 2008 01:16:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2008/03/10/132756.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132756.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2008/03/10/132756.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132756.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132756.html</trackback:ping><description><![CDATA[<a href="http://www.newsmth.net/bbstcon.php?board=CPlusPlus&amp;gid=218259">原文</a><br>本以为很简单，仔细阅读了一下 C++ 标准，发现内容还不少。总结了一下。<br><br>要点：<br><br>函数类型，函数名，变量名具有语言链接性，language linkage。<br><br>语言链接性可能会影响到名字以及调用约定等，由实现决定。<br><br>C++ 默认的语言连接性是 C++ 语言链接性。<br><br>语言链接性仅作用于函数类型，函数名，变量名。<br><br>不同语言链接性的函数类型是不同的类型，即便其余的地方都相同。<br><br>语言链接性用链接性规格（linkage-specification）来声明，分有无大括号两种形式。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern string-literal { declaration-seq opt }<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern string-literal declaration;<br><br>所有的实现都必须支持 "C" 和 "C++" 链接性。<br><br>链接性规格允许嵌套，此时最内层的那个起作用，但是并不建立作用域。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" { extern "C++" { class A{}; } }<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" { class B:A{};}<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;因为不是作用域，B 可以看到 A<br><br>如果C链接性施加到C++类成员和成员函数的类型，忽略C链接性，但是其余的地方依然有效，比如成员函数的参数。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;比如：<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" { class A{ void f(void (*p)()){} }; }<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;A 不是函数和变量，没有语言链接性<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;f 是 C++ 成员函数，忽略所指定的 C 链接性，如果在外层没指定别的，就是<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;C++链接性。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;p 具有 C 链接性。<br><br>除了C++链接性的函数外，同一个函数不带链接性规格的声明的函数不能早于带的。<br>如果前面声明了带链接性规格的形式，后面又出现了不带的形式，不受影响。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;比如 extern "C" void foo(); void foo(); 是可以的，反过来不行。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;但是 extern void foo(); extern "C++" void foo(); 可以。<br><br>特定名字的 C 语言链接性的函数最多只能有一个，即便放在不同的 namespace 里，<br>也是同一个函数，变量也是如此。这样的函数或变量也不能重复定义。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;namespace A { extern "C" void f(); }<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;namespace B { extern "C" void f(); }<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;两个 f 是同一个<br><br>有链接性规格的函数具有外部链接性。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;因此<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" void f();<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;static void f();<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;是错误的。<br><br>链接性规格，带大括号的形式，不影响里面的声明是声明还是定义。<br>单个声明的形式，视为 extern 限定符。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" int i; &nbsp; &nbsp; &nbsp; 是声明；<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" { int i; } &nbsp; 是定义；<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" { extern int i; } 又是声明。<br><br>单个声明形式的连接性规格，不能再指定存储类别。<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" static void f(); // error<br><br>其他说明：<br>因为语言链接性只跟链接性有关，因此 C 语言链接性的函数也可以有默认参数，<br>函数的接口可以有引用。<br><br>标准 C++ 库中的符号默认是 C++ 链接性。<br>C++ 用的标准 C 库中的外部链接性符号可以是 C 或者 C++ 语言链接性，推荐后者。<br>任何带有连续两个下划线的名字保留给实现用作同时具有 C 和 C++ 链接性的名字。<br><br>标准 C 库中的任何函数签名都保留给实现用来做同时具有 C 和 C++ 链接性的名字。<br><br>因为不同语言连接性的相同签名的函数类型算作不同类型，所以可以相互重载，不算同<br>一个函数。<br>//commented by ctrlz<br>//这里应该是ill-formed. <br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C" int atexit(void (*f)(void)) &nbsp; // 这里的 f 是 C 链接性<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;extern "C++" int atexit(void (*f)(void)) // 这里的 f 是 C++ 链接性<br><br>类似的有两个原型的函数还有 bsearch，qsort 等带函数指针类型的参数的函数。<br><br>以上都是 C++ 标准里的理论，落实到实践上，为了实现方便，很多编译器都把 C 和 <br>C++ 链接性的函数的类型视为相同的类型，此时的链接性仅仅影响生成的名字，或者<br>还可能影响异常规格，比如把 extern "C" 的函数默认视为 throw() 的等，并不影响<br>调用约定。同样也为了实现的方便，C++ 所用的 C 库中的符号也都是 C 语言链接性的，<br>因此这样的情况下，atexit, bsearch, qsort 等在这些编译器搭配的库中也就只有一种<br>原型了。<br><br>这种情况下，同样签名的 C 和 C++ 链接性的函数类型就算做同种类型了，下边的程序<br>编译就会失败：<br>//commented by ctrlz.<br>//ill-formed<br>void f(void (*p)())<br>{<br>}<br>extern "C" void f(void (*q)())<br>{<br>}<br><br>VC 和 gcc 在这一点上都是不符合标准的。<br><br>链接性不应该影响函数的类型，就像普通函数的返回类型不影响函数的类型一样，从重载解<br>析的角度看，如果接受以上的行为，势必影响重载解析的过程。<img src="http://blog.vckbase.com/localvar/aggbug/32879.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132756.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2008-03-10 09:16 <a href="http://www.cppblog.com/localvar/archive/2008/03/10/132756.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>再记自己的两个常识性错误</title><link>http://www.cppblog.com/localvar/archive/2008/01/08/132759.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Tue, 08 Jan 2008 03:58:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2008/01/08/132759.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132759.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2008/01/08/132759.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132759.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132759.html</trackback:ping><description><![CDATA[<p>1. WSAStartup只要每个进程调用一次就行了<br>&nbsp;&nbsp;&nbsp; 不知为什么, 几年以来，我一直认为要为每个使用网络的线程调一次. 直到今天才发现弄错了, 按说我一直是仔细阅读msdn的, 唉! 不过为每个线程调一次只是多余的, 并不是错误的, 也许这就是我一直没有注意到它的原因吧.<br>2. do while循环中的continue会跳到哪里</p>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><span style="COLOR: #0000ff">do</span><span style="COLOR: #000000">&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_3_42_Closed_Text></span><span id=Codehighlighter1_3_42_Open_Text><span style="COLOR: #000000">{<br>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;①</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;i</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">continue</span><span style="COLOR: #000000">;<br>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;②</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">}</span></span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">(&nbsp;i&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">10</span><span style="COLOR: #000000">&nbsp;);</span></div>
</div>
<p>&nbsp;&nbsp;&nbsp; 一直认为是①, 今天正在写的程序出错了才发现是②. 老天保佑以前的程序不出错吧<img border=0 src="http://www.cppblog.com/Emoticons/QQ/02.gif" width=20 height=20>. 这个错误一直没发现的原因有两点，一是我用do while循环比较少, 里面有continue的更少; 二是自己偷懒了, 想当然了, 其实以前怀疑过它的结果的, 但觉得①更符合逻辑就没有深究.</p>
<p>&nbsp;&nbsp;&nbsp; 犯了错误总是比较郁闷的, 不过能在一个上午认识到这样两个错误，也算收获不小了。</p>
<img src="http://blog.vckbase.com/localvar/aggbug/31689.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132759.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2008-01-08 11:58 <a href="http://www.cppblog.com/localvar/archive/2008/01/08/132759.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我的一个错误&amp;友情提示: 不要定义原型相同但实现不同的同名内联函数</title><link>http://www.cppblog.com/localvar/archive/2007/02/14/132779.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Wed, 14 Feb 2007 03:31:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2007/02/14/132779.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132779.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2007/02/14/132779.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132779.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132779.html</trackback:ping><description><![CDATA[<div>最早是在下面的程序中发现的问题:</div>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #ff9900">// a.cpp<br></span><span style="COLOR: #0000ff">struct</span> foo<br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">void</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"foo::bar in a.cpp\n"</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><br><span style="COLOR: #0000ff">void</span> testa<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;foo f<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;f<span style="COLOR: #0000cc">.</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #ff9900">// b.cpp<br></span><span style="COLOR: #0000ff">struct</span> foo<br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">void</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"foo::bar in b.cpp\n"</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><br><span style="COLOR: #0000ff">void</span> testb<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;foo f<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;f<span style="COLOR: #0000cc">.</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #ff9900">// main.cpp<br></span><span style="COLOR: #0000ff">int</span> main<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">int</span> argc<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000ff">char</span><span style="COLOR: #0000cc">*</span><span style="COLOR: #0000cc">*</span> argv <span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;testa<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;testb<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span></span></code></p>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000cc">&nbsp; return 0;</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
结果我发现这个程序居然输出:<br>foo::bar in a.cpp<br>foo::bar in a.cpp<br>先怀疑是编译器的bug, 不过很快否定了(因为编译阶段并不管具体链接的符号). 继而怀疑是链接器的bug, 但最后发现还是我的bug, 因为上面的代码实际上等价于
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #ff9900">// test.h<br></span><span style="COLOR: #0000ff">struct</span> foo<br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #0000ff">inline</span> <span style="COLOR: #0000ff">void</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><br><span style="COLOR: #ff9900">// a.cpp<br></span><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span> <span style="COLOR: #ff00ff">"test.h"</span><br><span style="COLOR: #0000ff">inline</span> <span style="COLOR: #0000ff">void</span> foo<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"foo::bar in a.cpp\n"</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #0000ff">void</span> testa<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;foo f<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;f<span style="COLOR: #0000cc">.</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #ff9900">// b.cpp<br></span><span style="COLOR: #0000cc">#</span><span style="COLOR: #ff0000">include</span> <span style="COLOR: #ff00ff">"test.h"</span><br><span style="COLOR: #0000ff">inline</span> <span style="COLOR: #0000ff">void</span> foo<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff0000">printf</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #ff00ff">"foo::bar in b.cpp\n"</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #0000ff">void</span> testb<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;foo f<span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;f<span style="COLOR: #0000cc">.</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span><br><br><span style="COLOR: #ff9900">// main.cpp<br></span><span style="COLOR: #0000ff">int</span> main<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">int</span> argc<span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000ff">char</span><span style="COLOR: #0000cc">*</span><span style="COLOR: #0000cc">*</span> argv <span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;testa<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>&nbsp;&nbsp;testb<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span></span></code></p>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000cc">&nbsp;&nbsp;return 0;</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
由于foo::bar是inline的, 所以允许每个编译单元有一个自己的实现, 但在链接阶段, 链接器是不可能发现这两个实现不同的(理论上可能,但工作量太大了), 所以它会假设实现相同(毕竟大家一般都把inline函数写在头文件中), 并把所有的引用指向同一个实现(如果分别指向本编译单元内部自有的实现肯定是错的, 考虑函数中定义了静态变量的情形), 这也就出现了本文中的问题. 如果foo::bar不是inline的, 则会有一个链接错误.<br>这里bar是作为一个成员函数出现的, 但它是非成员函数也有同样的问题, 所以结论就是: 不要在同一个名字空间中定义原型相同但实现不同的同名内联函数.<br><br>ps: 狗年最后一篇，祝大家新春愉快，万事如意，合家欢乐.<img src="http://blog.vckbase.com/localvar/aggbug/24589.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2007-02-14 11:31 <a href="http://www.cppblog.com/localvar/archive/2007/02/14/132779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++中的序列点</title><link>http://www.cppblog.com/localvar/archive/2007/02/07/132780.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Wed, 07 Feb 2007 11:55:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2007/02/07/132780.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132780.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2007/02/07/132780.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132780.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132780.html</trackback:ping><description><![CDATA[<a href="http://www.newsmth.net/bbstcon.php?board=CPlusPlus&amp;gid=177091">原文链接</a><br>
<h4>0. 什么是副作用（side effects）</h4>
<div>C99定义如下<br>Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.</div>
<div>C++2003定义如下<br>Accessing an object designated by a volatile lvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.</div>
<div>可以看出C99和C++2003对副作用的定义基本类似，一个程序可以看作一个状态机，在任意一个时刻程序的状态包含了它的所有对象内容以及它的所有文件内容（标准输入输出也是文件），副作用会导致状态的跳转</div>
<div>一个变量一旦被声明为volatile-qualified类型，则表示该变量的值可能会被程序之外的事件改变，每次读取出来的值只在读取那一刻有效，之后如果再用到该变量的值必须重新读取，不能沿用上一次的值，因此读取volatile-qualified类型的变量也被认为是有副作用，而不仅仅是改写</div>
<div>注，一般不认为程序的状态包含了CPU寄存器的内容，除非该寄存器代表了一个变量，例如</div>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">void</span> foo<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;<span style="COLOR: #0000ff">register</span> <span style="COLOR: #0000ff">int</span> i <span style="COLOR: #0000cc">=</span> 0<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 变量i被直接放入寄存器中，本文中被称为寄存器变量<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 注，register只是一个建议，不一定确实放入寄存器中<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 而且没有register关键字的auto变量也可能放入寄存器<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 这里只是用来示例，假设i确实放入了寄存器中<br></span>&nbsp;&nbsp;i <span style="COLOR: #0000cc">=</span> 1<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 寄存器内容改变，对应了程序状态的改变，该语句有副作用<br></span>&nbsp;&nbsp;i <span style="COLOR: #0000cc">+</span> 1<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 编译时该语句一般有警告：&#8220;warning: expression has no effect&#8221;<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// CPU如果执行这个语句，也肯定会改变某个寄存器的值，但是程序状态<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 并未改变，除了代表i的寄存器，程序状态不包含其他寄存器的内容，<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 因此该语句没有任何副作用<br></span><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>特别的，C99和C++2003都指出，no effect的expression允许不被执行<br>An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).</p>
<h4><br>1. 什么是序列点（sequence points）</h4>
<p>C99和C++2003对序列点的定义相同<br>At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.</p>
<p>中文表述为，序列点是一些被特别规定的位置，要求在该位置前的evaluations所包含的一切副作用在此处均已完成，而在该位置之后的evaluations所包含的任何副作用都还没有开始</p>
<p>例如C/C++都规定完整表达式（full-expression）后有一个序列点</p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">,</span> j<span style="COLOR: #0000cc">;</span><br>i <span style="COLOR: #0000cc">=</span> 0<span style="COLOR: #0000cc">;</span><br>j <span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>上面的代码中i = 0以及j = i都是一个完整表达式，;说明了表达式的结束，因此在;处有一个序列点，按照序列点的定义，要求在i = 0之后j = i之前的那个序列点上对i = 0的求值以及副作用全部结束（0被写入i中），而j = i的任何副作用都还没有开始。由于j = i的副作用是把i的值赋给j，而i = 0的副作用是把i赋值为0，如果i = 0的副作用发生在j = i之后，就会导致赋值后j的值是i的旧值，这显然是不对的</p>
<p>由序列点以及副作用的定义很容易看出，在一个序列点上，所有可能影响程序状态的动作均已完成，那这样能否推断出在一个序列点上一个程序的状态应该是确定的呢？！答案是不一定，这取决于我们代码的写法。但是，如果在一个序列点上程序的状态不能被确定，那么标准规定这样的程序是undefined behavior，稍后会解释这个问题</p>
<h4><br>2. 表达式求值（evaluation of expressions）与副作用发生的相互顺序</h4>
<p>C99和C++2003都规定<br>Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.</p>
<p>也就是说，C/C++都指出一般情况下在表达式求值过程中的操作数求值顺序以及副作用发生顺序是未说明的（unspecified）。为什么C/C++不详细定义这些顺序呢？原因是因为C/C++都是极端追求效率的语言，不规定这些顺序，是为了允许编译器有更大的优化余地，例如<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> <span style="COLOR: #0000cc">*</span>p<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">*</span>p <span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;</span> </span></code></p>
            </td>
        </tr>
    </tbody>
</table>
根据前述规定，在表达式(1)中到底是*p先被求值还是i++先被求值是由编译器决定的；两次副作用（对*p赋值以及i++）发生的顺序是由编译器决定的；甚至连子表达式i++的求值（就是初始时i的值）以及副作用（将i增加1）都不需要同步发生，编译器可以先用初始时i的值（即子表达式i++的值）对*p赋值，然后再将i增加1，这样就把子表达式i++的整个计算过程分成了两个不相邻的步骤。而且通常编译器都是这么实现的，原因在于i++的求值过程同*p = i++是有区别的，对于单独的表达式i++，执行顺序一般是（假设不考虑inc指令）：先将i加载到某个寄存器A（如果i是寄存器变量则此步骤可以跳过）、将寄存器A的值加1、将寄存器A的新值写回i的地址；对于*p = i++，如果要先完整的计算子表达式i++，由于i++表达式的值是i的旧值，因此还需要一个额外的寄存器B以及一条额外的指令来辅助*p = i++的执行，但是如果我们先将加载到A的值写回到*p，然后再执行对i增加1的指令，则只需要一个寄存器即可，这种做法在很多平台都有重要意义，因为寄存器的数目往往是有限的，特别是假如有人写出如下的语句<br>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">,</span> j<span style="COLOR: #0000cc">,</span> k<span style="COLOR: #0000cc">,</span> x<span style="COLOR: #0000cc">;</span><br>x <span style="COLOR: #0000cc">=</span> <span style="COLOR: #0000cc">(</span>i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">+</span> <span style="COLOR: #0000cc">(</span>j<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">+</span> <span style="COLOR: #0000cc">(</span>k<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>编译器可以先计算(i++) + (j++) + (k++)的值，然后再对i、j、k各自加1，最后将i、j、k、x写回内存，这比每次完整的执行完++语义效率要高</p>
<h4><br>3. 序列点对副作用的限制</h4>
<p>C99和C++2003都有类似的如下规定<br>Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.</p>
<p>也就是说，在相邻的两个序列点之间，一个对象只允许被修改一次，而且如果一个对象被修改则在这两个序列点之间对该变量的读取的唯一目的只能是为了确定该对象的新值（例如i++，需要先读取i的值以确定i的新值是旧值+1）。特别的，标准要求任意可能的执行顺序都必须满足该条件，否则代码将是undefined behavior</p>
<p>之所以序列点会对副作用有如此的限制，就是因为C/C++标准没有规定子表达式求值以及副作用发生之间的顺序，例如<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">,</span> a<span style="COLOR: #0000cc">[</span><span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> foo<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #0000cc">,</span> <span style="COLOR: #0000ff">int</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span><br>i <span style="COLOR: #0000cc">=</span> <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span>i <span style="COLOR: #0000cc">+</span> 1<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 该表达式对i所做的两次修改都需要写回对象，i的最终值取决<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 于到底哪次写回最后发生，如果赋值动作最后写回，则i的值<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 是i的旧值加2，如果++i动作最后写回，则i的值是旧值加1，<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 因此该表达式的行为是undefined<br></span>a<span style="COLOR: #0000cc">[</span>i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">]</span> <span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 如果=左边的表达式先求值并且i++的副作用被完成，则右边的<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 值是i的旧值加1，如果i++的副作用最后完成，则右边的值是i<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 的旧值，这也导致了不确定的结果，因此该表达式的行为将是<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// undefined<br></span>foo<span style="COLOR: #0000cc">(</span>foo<span style="COLOR: #0000cc">(</span>0<span style="COLOR: #0000cc">,</span> i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">,</span> i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 对于函数调用而言，标准没有规定函数参数的求值<br></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;<span style="COLOR: #ff9900">// 顺序，但是标准规定所有参数求值完毕进入函数体<br></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;<span style="COLOR: #ff9900">// 执行之前有一个序列点，因此这个表达式有两种执<br></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;<span style="COLOR: #ff9900">// 行方式，一种是先求值外层foo调用的i++然后求值<br></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;<span style="COLOR: #ff9900">// foo(0, i++)，然后进入到foo(0, i++)执行，这之<br></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;<span style="COLOR: #ff9900">// 前有个序列点，这种执行方式还是在两个相邻序列<br></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;<span style="COLOR: #ff9900">// 点之间修改了i两次，undefined<br></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;<span style="COLOR: #ff9900">// 另一种执行方式是先求值foo(0, i++)，由于这里<br></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;<span style="COLOR: #ff9900">// 有一个序列点，随后的第二个i++求值是在新序列<br></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;<span style="COLOR: #ff9900">// 点之后，因此不算是两个相邻的序列点之间修改i<br></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;<span style="COLOR: #ff9900">// 两次<br></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;<span style="COLOR: #ff9900">// 但是，前面已经指出标准规定任意可能的执行路径<br></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;<span style="COLOR: #ff9900">// 都必须满足条件才是定义好的行为，这种代码仍然<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=#ff9933>// 是undefined</font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>前面我提到在一个序列点上程序的状态不一定是确定的，原因就在于相邻的两个序列点之间可能会发生多个副作用，这些副作用的发生顺序是未指定的，如果多于一个的副作用用于修改同一个对象，例如示例代码i = ++i + 1;，则程序的结果是依赖于副作用发生顺序的；另外，如果某个表达式既修改了某个对象又需要读取该对象的值，且读取对象的值并不用于确定对象新值，则读取和修改两个动作的先后顺序也会导致程序的状态不能唯一确定<br>所幸的是，&#8220;在相邻的两个序列点之间，一个对象只允许被修改一次，而且如果一个对象被修改则在这两个序列点之间只能为了确定该对象的新值而读一次&#8221;这一强制规定保证了符合要求的程序在任何一个序列点位置上其状态都可以确定下来</p>
<p>注，由于对于UDT类型存在operator重载，函数语义会提供新的序列点，因此某些对于built-in类型是undefined behavior的表达式对于UDT确可能是良好定义的，例如<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000">i <span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 如果i是built-in类型对象，则该表达式在两个相邻的序列点之间对<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// i修改了两次，undefined<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 如果i是UDT类型该表达式也许是i.operator=(i.operator++(int))，<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 函数参数求值完毕后会有一个序列点，因此该表达式并没有在两个<br></span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// 相邻的序列点之间修改i两次，OK<br></span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>由此可见，常见的问题如printf("%d, %d", i++, i++)这种写法是错误的，这类问题作为笔试题或者面试题是没有任何意义的<br>类似的问题同样发生在cout &lt;&lt; i++ &lt;&lt; i++这种写法上，如果overload resolution选择成员函数operator&lt;&lt;，则等价于(cout.operator&lt;&lt;(i++)).operator&lt;&lt;(i++)，否则等价于operator&lt;&lt;(operator&lt;&lt;(cout, i++), i++)，如果i是built-in类型对象，这种写法跟foo(foo(0, i++), i++)的问题一致，都是未定义行为，因为存在某条执行路径使得i会在两个相邻的序列点之间被修改两次；如果i是UDT则该写法是良好定义的，跟i = i++一样，但是这种写法也是不推荐的，因为标准对于函数参数的求值顺序是unspecified，因此哪个i++先计算是不能预计的，这仍旧会带来移植性的问题，这种写法应该避免<br></p>
<h4>4. 编译器的跨序列点优化</h4>
<p>根据前述讨论可知，在同一个表达式内对于同一个变量i，允许的行为是<br>A. 不读取，改写一次，例如</p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000">i <span style="COLOR: #0000cc">=</span> 0<span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
B. 读取一次或者多次，改写一次，但所有读取仅仅用于决定改写后的新值，例如
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000">i <span style="COLOR: #0000cc">=</span> i <span style="COLOR: #0000cc">+</span> 1<span style="COLOR: #0000cc">;</span> <span style="COLOR: #ff9900">// 读取一次，改写一次<br></span>i <span style="COLOR: #0000cc">=</span> i <span style="COLOR: #0000cc">&amp;</span> <span style="COLOR: #0000cc">(</span>i <span style="COLOR: #0000cc">-</span> 1<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> </span></code></p>
            </td>
        </tr>
    </tbody>
</table>
C. 不改写，读取一次或者多次，例如
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000">j <span style="COLOR: #0000cc">=</span> i <span style="COLOR: #0000cc">&amp;</span> <span style="COLOR: #0000cc">(</span>i <span style="COLOR: #0000cc">-</span> 1<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>对于情况B和C，编译器是有一定的优化权利的，它可以只读取一次变量的值然后直接使用该值多次</p>
<p>但是，当该变量是volatile-qualified类型时编译器允许的行为究竟如何目前还没有找到明确的答案，ctrlz认为如果在两个相邻序列点之间读取同一个volatile-qualified类型对象多次仍旧是undefined behavior，原因在于该读取动作有副作用且该副作用等价于修改该对象，RoachCock的意见是两个相邻的序列点之间读取同一个volatile-qualified类型应该是合法的，但是不能被优化成只读一次。一段在嵌入式开发中很常见的代码示例如下<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">volatile</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">if</span> <span style="COLOR: #0000cc">(</span>i <span style="COLOR: #0000cc">!</span><span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff9900">// 探测很短的时间内i是否发生了变化<br></span>&nbsp;&nbsp;<span style="COLOR: #ff9900">// ...<br></span><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
如果i != i被优化为只读一次，则结果恒为false，故RoachCock认为编译器不能够对volatile-qualified类型的变量做出只读一次的优化。ctrlz则认为这段代码本身是不正确的，应该改写成<br>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">int</span> j <span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">if</span> <span style="COLOR: #0000cc">(</span>j <span style="COLOR: #0000cc">!</span><span style="COLOR: #0000cc">=</span> i<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff9900">// 将对volatile-qualified类型变量的多次读取用序列点隔开<br></span>&nbsp;&nbsp;<span style="COLOR: #ff9900">// ...<br></span><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
虽然尚不能确定volatile-qualified类型的变量在相邻两个序列点之间读取多次行为是否合法以及将如何优化（不管怎么样，对于volatile-qualified类型这种代码应该尽量避免），但是可以肯定的是，对于volatile-qualified类型的变量在跨序列点之后必须要重新读取，volatile就是用来阻止编译器做出跨序列点的过激优化的，而对于non-volatile-qualified类型的跨序列点多次读取则可能被优化成只读一次（直到某个语句或者函数对该变量发生了修改，在此之前编译器可以假定non-volatile-qualified类型的变量是不会变化的，因为目前的C/C++抽象机器模型是单线程的），例如<br>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">bool</span> flag <span style="COLOR: #0000cc">=</span> <span style="COLOR: #0000ff">true</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">void</span> foo<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;<span style="COLOR: #0000ff">while</span> <span style="COLOR: #0000cc">(</span>flag<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">{</span> <span style="COLOR: #ff9900">// (2)<br></span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: #ff9900">// ...<br></span>&nbsp;&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>如果编译器探测到foo()没有任何语句（包括foo()调用过的函数）对flag有过修改，则也许会把(2)优化成只在进入foo()的时候读一次flag的值而不是每次循环都读一次，这种跨序列点的优化很有可能导致死循环。但是这种代码在多线程编程中很常见，虽然foo()没有修改过flag，也许在另一个线程的某个函数调用中会修改flag以终止循环，为了避免这种跨序列点优化带来到错误，应该把flag声明为volatile bool，C++2003对volatile的说明如下<br>[Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. ]<br><br></p>
<h4>5. C99定义的序列点列表</h4>
<p>— The call to a function, after the arguments have been evaluated.<br>— The end of the first operand of the following operators: <br>&nbsp;&nbsp;&nbsp;&nbsp; logical AND &amp;&amp; ; <br>&nbsp;&nbsp;&nbsp;&nbsp; logical OR || ; <br>&nbsp;&nbsp;&nbsp;&nbsp; conditional ? ; <br>&nbsp;&nbsp;&nbsp;&nbsp; comma , .<br>— The end of a full declarator: <br>&nbsp;&nbsp;&nbsp;&nbsp; declarators;<br>— The end of a full expression: <br>&nbsp;&nbsp;&nbsp;&nbsp; an initializer; <br>&nbsp;&nbsp;&nbsp;&nbsp; the expression in an expression statement; <br>&nbsp;&nbsp;&nbsp;&nbsp; the controlling expression of a selection statement (if or switch); <br>&nbsp;&nbsp;&nbsp;&nbsp; the controlling expression of a while or do statement; <br>&nbsp;&nbsp;&nbsp;&nbsp; each of the expressions of a for statement; <br>&nbsp;&nbsp;&nbsp;&nbsp; the expression in a return statement.<br>— Immediately before a library function returns.<br>— After the actions associated with each formatted input/output function conversion specifier.<br>— Immediately before and immediately after each call to a comparison function, and also between any call to a comparison function and any movement of the objects passed as arguments to that call.</p>
<h4><br>6. C++2003定义的序列点列表</h4>
<p>所有C99定义的序列点同样是C++2003所定义的序列点<br>此外，C99只是规定库函数返回之后有一个序列点，并没有规定普通函数返回之后有一个序列点，而C++2003则特别指出，进入函数（function-entry）和退出函数（function-exit）各有一个序列点，即拷贝一个函数的返回值之后同样存在一个序列点</p>
<p>需要特别说明的是，由于operator||、operator&amp;&amp;以及operator,可以重载，当它们使用函数语义的时候并不提供built-in operators所规定的那几个序列点，而仅仅只是在函数的所有参数求值后有一个序列点，此外函数语义也不支持||、&amp;&amp;的短路语义，这些变化很有可能会导致难以发觉的错误，因此一般不建议重载这几个运算符</p>
<h4><br>7. C++2003中两处关于lvalue的修改对序列点的影响</h4>
<p>在C语言中，assignment operators的结果是non-lvalue，C++2003则将assignment operators的结果改成了lvalue，目前尚不清楚这一改动对于built-in类型有何意义，但是它却导致了很多在合法的C代码在目前的C++中是undefined behavior，例如<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> j<span style="COLOR: #0000cc">;</span><br>i <span style="COLOR: #0000cc">=</span> j <span style="COLOR: #0000cc">=</span> 1<span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
由于(j = 1)的结果是lvalue，该结果作为给i赋值的右操作数，需要一个lvalue-to-rvalue conversion，这个conversion代表了一个读取语义，因此i = j = 1就是先将1赋值给j，然后读取j的值赋值给i，这个行为是undefined，因为标准规定两个相邻序列点之间的读取只能用于决定修改对象的新值，而不能发生在修改之后再读取<br>由于C++2003规定assignment operators的结果是lvalue，因此下列在C99中非法的代码在C++2003中却是可以通过编译的
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">(</span>i <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">=</span> 1<span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">=</span> 2<span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>显然按照C++2003的规定这个代码的行为是undefined，它在两个相邻的序列点之间修改了i两次</p>
<p>类似的问题同样发生在built-in类型的前缀++/--operators上，C++2003将前缀++/--的结果从rvalue修改为lvalue，这甚至导致了下列代码也是undefined behavior<br></p>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> i<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">extern</span> <span style="COLOR: #0000ff">int</span> j<span style="COLOR: #0000cc">;</span><br>i <span style="COLOR: #0000cc">=</span> <span style="COLOR: #0000cc">+</span><span style="COLOR: #0000cc">+</span>j<span style="COLOR: #0000cc">;</span></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
<p>同样是因为lvalue作为assignment operator的右操作数需要一个左值转换，该转换导致了一个读取动作且这个读取动作发生在修改对象之后</p>
<p>C++的这一改动显然是考虑不周的，导致了很多C语言的习惯写法都成了undefined behavior，因此Andrew Koenig在1999年的时候就向C++标准委员会提交了一个建议要求为assignment operators增加新的序列点，但是到目前为止C++标准委员会都还没有就该问题达成一致意见</p>
<div></div>
<img src="http://blog.vckbase.com/localvar/aggbug/24495.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132780.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2007-02-07 19:55 <a href="http://www.cppblog.com/localvar/archive/2007/02/07/132780.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>sizeof的计算</title><link>http://www.cppblog.com/localvar/archive/2006/12/03/132736.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Sat, 02 Dec 2006 23:44:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2006/12/03/132736.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132736.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2006/12/03/132736.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132736.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132736.html</trackback:ping><description><![CDATA[<font face=宋体>&nbsp;&nbsp;&nbsp; 论坛上经常有人问某个结构体的大小为什么与他预计的不一致, 对特定问题，我肯定能回答出来; 但要从总体上说出个子丑寅卯, 我就不行了. 今日闲来无事, 特地研究了一下, 总算将sizeof的计算方法大概梳理清楚了.</font>
<h3><span style="FONT-FAMILY: 宋体">1. 字节对齐</span></h3>
<p><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; 每种cpu都有其特定的字长, 如目前最常见的32位cpu, 其字长就是32位(4字节), 即将普及的64位cpu, 字长就是64位(8字节). 而cpu对内存的访问总是从字长的整数倍开始, 以字长为单位的. 例如(假设是32位cpu, 下同)读一个32位整数, 如果其起始地址是0, 则只需一次内存操作; 但如果起始地址是1、2或3, 则需要两次操作来将相关的两个字都读出来, 再在cpu内部进行处理才行. 对x86cpu, 这样做仅会稍微降低一点效率, 但如果是某些其它类型的cpu则可能会有成千上万倍的效率损失, 甚至是程序根本无法运行. </span></p>
<p><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; 从以上的分析可以看出, 我们定义结构体时, 为了提高效率和可移植性, 应该尽量让cpu以最少的内存操作访问其任意一个成员. 例如对于第一个成员是char型, 第二个成员是int型的结构, 其体积应该是5字节, 但显然在这种情况下cpu对int型成员的访问要两次内存操作, 而不是最少的一次. 所以我们应该浪费一些空间, 在char型成员后面加上3个填充字节, 使int型成员的起始位置对齐cpu字长的整数倍. 不过, 一个像回事的程序会有大量结构定义, 如果都去人为计算, 手工填充, 工作量就太大了. 因此, 编译器特意设置了一个选项(在vs2005中, 它是项目属性|c/c++|code generation|struct member alignment)来配置默认的字节对齐方式, 你可以选择1、2、4、8或者16字节对齐. 如果你什么也没有选的话, 它的默认值是8. 当然, 编译器也并没有搞一刀切, 对于需要特殊处理的结构, 你还可以使用"#pragma pack"编译指令单独为其指定其合适的对齐方式. 注意, 虽然我们从cpu字长引出了编译器的字节对齐, 但它们是两个不同的概念, 一定不要弄混. 从某种意义上, 你可以把字节对齐理解为: 我的程序将运行在字长是这个数值的cpu上.</span></p>
<h3><span style="FONT-FAMILY: 宋体">2. 简单结构</span></h3>
<p><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; 这里所说的简单结构, 是指仅由编译器内置类型构成的结构. 我们已经知道, 计算其体积, 需要其字节对齐设置, 这里假设其值是N. 则(有点拗口, 多读几遍):</span><br><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; 简单结构的大小, 是其符合"使cpu在任意情况下, 访问其任意成员的内存操作次数不大于'(sizeof(此成员) + N - 1)/N'(这里的除法使用的是C/C++整数除法的语意)"的所有内存布局中, 体积最小的那个占用的字节数.</span>&nbsp;<br><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; 例如结构：<br>&nbsp;&nbsp; &nbsp;<span style="COLOR: #0000ff">struct</span> A &nbsp; &nbsp; &nbsp; &nbsp; 和 &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #3300ff">struct</span> B<br>&nbsp;&nbsp; &nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; {<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; <span style="COLOR: #3300ff">char</span> c; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #3300ff">double</span> d;<br>&nbsp; &nbsp; &nbsp;&nbsp; &nbsp;<span style="COLOR: #3300ff">double</span> d; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #3300ff">char</span> c;<br>&nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; };<br>其sizeof的结果与N的对应关系应该是:</span>
<p>
<div style="TEXT-ALIGN: center">
<table style="WIDTH: 60%">
    <tbody>
        <tr>
            <td style="WIDTH: 100px"><strong>N</strong></td>
            <td style="WIDTH: 100px"><strong>sizeof(A)</strong></td>
            <td style="WIDTH: 100px"><strong>sizoef(B)</strong></td>
        </tr>
        <tr>
            <td style="WIDTH: 100px">1</td>
            <td style="WIDTH: 100px">9</td>
            <td style="WIDTH: 100px">9</td>
        </tr>
        <tr>
            <td style="WIDTH: 100px">2</td>
            <td style="WIDTH: 100px">10</td>
            <td style="WIDTH: 100px">10</td>
        </tr>
        <tr>
            <td style="WIDTH: 100px; HEIGHT: 21px">4</td>
            <td style="WIDTH: 100px; HEIGHT: 21px">12</td>
            <td style="WIDTH: 100px; HEIGHT: 21px">12</td>
        </tr>
        <tr>
            <td style="WIDTH: 100px">8</td>
            <td style="WIDTH: 100px">16</td>
            <td style="WIDTH: 100px">16</td>
        </tr>
        <tr>
            <td style="WIDTH: 100px">16</td>
            <td style="WIDTH: 100px">16</td>
            <td style="WIDTH: 100px">16</td>
        </tr>
    </tbody>
</table>
</div>
<span style="FONT-FAMILY: 宋体"><br>&nbsp; &nbsp; A的结果我想大家都能计算出来, 就不多解释了. 但你可能会问: 当N&gt;1时, B的结果为什么不是9了呢? 它是9才是符合你上面的描述呀?! 这里你就要注意我所说的"任意情况"了, 如果我们总是使用单个的B, 那sizeof(B)总是9肯定没问题, 但问题是我们有时会定义一个B的数组, 如: <br>&nbsp; &nbsp; B array[2];<br>这时, 如果我们访问"array[1].d"会需要几次内存操作呢? 不用说, 它超过了"(sizeof(d) + N - 1)/N", 所以它的大小不能总是9.<br></span>
<h3><span style="FONT-FAMILY: 宋体">3. 复合结构</span></h3>
<p><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; &nbsp;与简单结构相对, 复合结构就是指那些部分或全部成员也是结构的结构. 其实没有必要把它单独列出来的, 因为其大小的计算方法与简单结构完全一样. 唯一要注意的就是: 对其每个成员的大小的计算要按定义这个成员的类型时的字节对齐来进行, 而不能按定义这个复合结构时的对齐设置. 例如:</span> <br><span style="FONT-FAMILY: 宋体">&nbsp; &nbsp; <span style="COLOR: #3300ff">#pragma pack</span>( <span style="COLOR: #3300ff">push</span>, 8 )<br>&nbsp; &nbsp; <span style="COLOR: #3300ff">struct</span> A<br>&nbsp; &nbsp; {<br>&nbsp;&nbsp; &nbsp; &nbsp;&nbsp; <span style="COLOR: #3300ff">char</span> c;<br>&nbsp;&nbsp; &nbsp; &nbsp;&nbsp; &nbsp;<span style="COLOR: #3300ff">double</span> d;<br>&nbsp; &nbsp; };<br>&nbsp; &nbsp; <span style="COLOR: #3300ff">#pragma pack</span>( 1 )<br>&nbsp; &nbsp; <span style="COLOR: #3300ff">struct</span> B<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #3300ff">char</span> c;<br>&nbsp;&nbsp; &nbsp; &nbsp;&nbsp; &nbsp;A a;<br>&nbsp; &nbsp; };<br>&nbsp; &nbsp; <span style="COLOR: #3300ff">#pragma pack</span>( <span style="COLOR: #3300ff">pop</span> )<br>sizeof(A)和sizeof(B), 将分别是16和17, 而不是16和10.</span>
<h3><span style="FONT-FAMILY: 宋体">4. 特殊情况</span></h3>
<p><span style="FONT-FAMILY: 宋体">&nbsp;&nbsp; &nbsp;结构体的大小还有几种特殊情况, 一是空结构, 即没有任何成员的结构, 这种情况, 编译器规定其大小是1; 二是如果结构有虚函数, 则它会包含一个虚函数表指针(vfptr, 但这只是一般情况, 具体取决与编译器实现), 它的大小等于cpu字长, 但由于不能直接从定义中看出来, 你数的时候一定不要把它忘了; 还有就是虚拟继承等情况, 不过它们的结果与编译器实现关联太严重, 并且通常也碰不到, 这里就不做讨论了.</span></p>
<p><span style="FONT-FAMILY: 宋体">PS: 只是大概想了这么多, 如果哪里说错了, 欢迎大家批评指正.</span></p>
<img src="http://blog.vckbase.com/localvar/aggbug/23337.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132736.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2006-12-03 07:44 <a href="http://www.cppblog.com/localvar/archive/2006/12/03/132736.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用派生类对象通过成员函数指针调用基类虚函数之不可能性的证明</title><link>http://www.cppblog.com/localvar/archive/2006/10/19/132739.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Thu, 19 Oct 2006 11:32:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2006/10/19/132739.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132739.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2006/10/19/132739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132739.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132739.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp;&nbsp; 希望大家没有被这么拗口的标题吓到:). 本文源于下面这个问题:</div>
<div>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><font face=新宋体><span style="COLOR: #0000ff">struct</span> base<br><span style="COLOR: #0000cc">{</span><br>&nbsp;<span style="COLOR: #0000ff">void</span> foo<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br>&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;<span style="COLOR: #ff0000">cout</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff00ff">"base::foo"</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff0000">endl</span><span style="COLOR: #0000cc">;</span><br>&nbsp;<span style="COLOR: #0000cc">}</span><br>&nbsp;<span style="COLOR: #0000ff">virtual</span> <span style="COLOR: #0000ff">void</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br>&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;<span style="COLOR: #ff0000">cout</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff00ff">"base::bar"</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff0000">endl</span><span style="COLOR: #0000cc">;</span><br>&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">struct</span> derived <span style="COLOR: #0000cc">:</span> base<br><span style="COLOR: #0000cc">{</span><br>&nbsp;<span style="COLOR: #0000ff">virtual</span> <span style="COLOR: #0000ff">void</span> bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><br>&nbsp;<span style="COLOR: #0000cc">{</span><br>&nbsp;&nbsp;<span style="COLOR: #ff0000">cout</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff00ff">"derived::bar"</span> <span style="COLOR: #0000cc">&lt;</span><span style="COLOR: #0000cc">&lt;</span> <span style="COLOR: #ff0000">endl</span><span style="COLOR: #0000cc">;</span><br>&nbsp;<span style="COLOR: #0000cc">}</span><br><span style="COLOR: #0000cc">}</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">int</span> __cdecl _tmain<span style="COLOR: #0000cc">(</span> <span style="COLOR: #0000ff">int</span> argc<span style="COLOR: #0000cc">,</span> _TCHAR<span style="COLOR: #0000cc">*</span> argv<span style="COLOR: #0000cc">[</span><span style="COLOR: #0000cc">]</span> <span style="COLOR: #0000cc">)</span><br><span style="COLOR: #0000cc">{</span><br>&nbsp;<span style="COLOR: #0000ff">void</span> <span style="COLOR: #0000cc">(</span>base<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">*</span>pfn<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span> <span style="COLOR: #0000cc">=</span> <span style="COLOR: #0000cc">&amp;</span>base<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span>bar<span style="COLOR: #0000cc">;</span><br>&nbsp;derived d<span style="COLOR: #0000cc">;</span><br>&nbsp;d<span style="COLOR: #0000cc">.</span>base<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span>bar<span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> </font><font face=新宋体><span style="COLOR: #ff9900">// 1<br></span><br>&nbsp;<span style="COLOR: #0000cc">(</span>d<span style="COLOR: #0000cc">.</span>base<span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">:</span><span style="COLOR: #0000cc">*</span>pfn<span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">(</span><span style="COLOR: #0000cc">)</span><span style="COLOR: #0000cc">;</span> </font><font face=新宋体><span style="COLOR: #ff9900">// 2 想实现和上一行一样的输出, 但是编译失败<br></span><br>&nbsp;<span style="COLOR: #0000ff">return</span> 0<span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000cc">}</span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
</div>
<div>&nbsp;&nbsp;&nbsp; 很明显, 标2的那一行是想使用派生类对象通过成员函数指针调用基类虚函数, 以实现与标1的那行相同的输出, 但却无法编译通过. 这是个语法错误, 因为"::"运算符的优先级高于 ".", 所以那一行会先计算"base::*pfn", 然而"*pfn"并不是"base"的成员, 故有错误时很自然的. 那么是否可以通过修改那条语句来达到目的呢? 分析了成员函数指针的实现后, 我发现, 至少在vc7.1和vc8上, 这是不可能的. 由于vc的标准兼容性已经非常高,所以我怀疑c++标准就不支持这种调用, 但没有证实.</div>
<div>&nbsp;&nbsp;&nbsp; 在上面的例子中, pfn指向的是虚函数bar, 但它也必须能指向普通成员函数foo. 当指向 foo时, 它保存的就是foo的入口地址; 然而当指向bar时, 直接保存这个地址就不行了,因为对base和derived来说, 这个地址并不相同. vc对此的解决方法是由编译器加入了一系列的内部函数"vcall". 一个类中的每个虚函数都有一个唯一与之对应的vcall函数, 但在不同类之间, 这些vcall实际上是公用的. pfn指向的就是这些vcall中的一个.</div>
<div>&nbsp;&nbsp;&nbsp; 我们知道, 调用成员函数时要传递this指针. 一般情况下, 它是通过ecx寄存器传递的, 所以vcall的实现如下所示:<br>
<table style="BORDER-COLLAPSE: collapse" border=1 cellSpacing=0 borderColor=#999999 cellPadding=0 width="75%" bgColor=#f1f1f1>
    <tbody>
        <tr>
            <td>
            <p style="LINE-HEIGHT: 150%; MARGIN: 5px"><code><span style="COLOR: #000000"><font face=新宋体><span style="COLOR: #0000ff">mov</span> <span style="COLOR: #ff0000">eax</span><span style="COLOR: #0000cc">,</span> <span style="COLOR: #ff0000">dword</span> <span style="COLOR: #0000ff">ptr</span><span style="COLOR: #0000cc">[</span><span style="COLOR: #ff0000">ecx</span><span style="COLOR: #0000cc">]</span><span style="COLOR: #0000cc">;</span><br><span style="COLOR: #0000ff">jmp</span> <span style="COLOR: #ff0000">dword</span> <span style="COLOR: #0000ff">ptr</span> <span style="COLOR: #0000cc">[</span><span style="COLOR: #ff0000">eax</span><span style="COLOR: #0000cc">+</span>xx<span style="COLOR: #0000cc">]</span></font></span></code></p>
            </td>
        </tr>
    </tbody>
</table>
第一句中, 由于ecx是this指针, 而一般vfptr是类的第一个成员, 所以它是把vfptr, 也就是vtable的地址存到了eax中. 第二句里面的xx, 在32位计算机上, 是4的整数倍, 所以这一句的意思是: 跳转到vtable的第xx/4项所指的地址上, 这个地址就是最终要调用的函数的入口.</div>
<div>&nbsp;&nbsp;&nbsp; 明白了虚成员函数指针的实现, 就可以看那种调用为什么不能实现了. 让我们用反证法, 如果它能实现, 为讨论方便就假设标2的那一句是正确的吧, 在进行调用时, 编译器首先要保证传递的是指向d的this指针, 然后还要保证this所指向的vfptr所指的是base 的vtable. 编译器能做到吗? 当然能, 它可以在调用前偷偷修改vfptr使其指向base的vtable, 并在调用返回后再把它恢复过来, 但想想这样做在多线程环境中的后果吧.&nbsp; 所以编译器是不可能这样做的, 也就是说"使用派生类对象通过成员函数指针调用基类虚函数是不可能的".<br><br><br>ps: 很早就写出来了，但一直忘了发:(</div>
<img src="http://blog.vckbase.com/localvar/aggbug/22811.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2006-10-19 19:32 <a href="http://www.cppblog.com/localvar/archive/2006/10/19/132739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>我的一个常识性错误</title><link>http://www.cppblog.com/localvar/archive/2006/10/08/132740.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Sun, 08 Oct 2006 01:17:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2006/10/08/132740.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132740.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2006/10/08/132740.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132740.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132740.html</trackback:ping><description><![CDATA[<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;foo(&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;bar[</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">]&nbsp;)<br><img id=Codehighlighter1_26_63_Open_Image onclick="this.style.display='none'; Codehighlighter1_26_63_Open_Text.style.display='none'; Codehighlighter1_26_63_Closed_Image.style.display='inline'; Codehighlighter1_26_63_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_26_63_Closed_Image onclick="this.style.display='none'; Codehighlighter1_26_63_Closed_Text.style.display='none'; Codehighlighter1_26_63_Open_Image.style.display='inline'; Codehighlighter1_26_63_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_26_63_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_26_63_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;printf(&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">%d </span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,&nbsp;</span><span style="COLOR: #0000ff">sizeof</span><span style="COLOR: #000000">(bar)&nbsp;);<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;main(&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;argc,&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">**</span><span style="COLOR: #000000">&nbsp;argv&nbsp;)<br><img id=Codehighlighter1_101_152_Open_Image onclick="this.style.display='none'; Codehighlighter1_101_152_Open_Text.style.display='none'; Codehighlighter1_101_152_Closed_Image.style.display='inline'; Codehighlighter1_101_152_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_101_152_Closed_Image onclick="this.style.display='none'; Codehighlighter1_101_152_Closed_Text.style.display='none'; Codehighlighter1_101_152_Open_Image.style.display='inline'; Codehighlighter1_101_152_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_101_152_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_101_152_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;bar[</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">];<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;foo(&nbsp;bar&nbsp;);<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span></div>
</div>
<p>一直认为输出应该是100, 但实际却是4(即sizeof(char*)). <br>现在，唯一让我庆幸的是我在函数参数中使用数组, 并且依赖sizeof的结果的时候极少.</p>
<p>&nbsp;</p>
<img src="http://blog.vckbase.com/localvar/aggbug/22673.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132740.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2006-10-08 09:17 <a href="http://www.cppblog.com/localvar/archive/2006/10/08/132740.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>给C++增加“取引用”运算符 </title><link>http://www.cppblog.com/localvar/archive/2005/09/14/132747.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Wed, 14 Sep 2005 03:47:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2005/09/14/132747.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132747.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2005/09/14/132747.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132747.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132747.html</trackback:ping><description><![CDATA[<h3>1. 程序实践的需要</h3>
&nbsp;&nbsp;&nbsp;&nbsp;首先问一个问题：在C++语言中，&#8220;int&#8221;和&#8220;int&amp;&#8221;是相同的数据类型吗？我想大家肯定回答：&#8220;当然不是，它们连兼容类型（如int和short的关系）都不是。&#8221;甚至有些人会想：这家伙脑袋是不是不正常，怎么问这么弱的问题？ <br>&nbsp;&nbsp;&nbsp;&nbsp;好，既然不是相同的类型，那以它们为区别的函数重载就应该是合法的、没有二义性的，但我相信你不试也知道下面的代码是编译不过去的，因为它有二义性。
<pre>	void foo( int a );
void foo( int&amp; a );
&#8230;&#8230;
int m = 10;
foo( m );     // 错误，二义性
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;而二义性的原因是程序员无法明确说明他想按引用传参还是按值传参，但这种情况很罕见，因为恐怕没有几个人会这样定义重载函数，所以问题不大。不过下面的情况就不一样了：
<pre>	//以下代码是甲写的
void foo( int a );
//以下代码是乙写的
int m = 10;
foo( m );
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;乙的代码调用甲的函数，一切良好，编译、链接、测试全部通过。但突然有一天，甲觉得让foo的参数是&#8220;int&amp;&#8221;会更好，所以他改了自己的程序，乙拿到甲的新版代码，发现编译链接毫无问题，就放心的发布了，与此同时，一个严重的bug也潜伏下来了。但这关C++什么事吗？甲应该告诉乙他对代码的修改，乙也应该对自己的产品进行完整的测试呀？是的，这两点没错，但我觉得C++也应该对此负责，因为它应该帮助乙避免这种错误，而且它在另外一种类似的情形下也确实这么做了，看下面的例子（参考Effective C++，Item 26编写）：
<pre>	//以下代码是甲写的
class Base1
{
public:
void doIt();
};
class Base2
{
private:
void doIt();
};
//以下代码是乙写的
// Devried没有定义自己的doIt
class Devried : public Base1, public Base2
{
};
&#8230;&#8230;
Devried d;
d.doIt();				//①
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;大家说①处的代码调的是哪一个doIt？当然是Base1的那个，因为Base2的那个是私有的，在这里无法访问；那①处的代码有二义性吗？当然没有，编译器可以很容易得知道其目的；那它能编译过去吗？当然不能，因为编译器不允许&#8220;因为改变类成员的保护级别而改变程序的含义&#8221;。在这里，如果编译器让它通过了，那么当有一天甲把Base1的改为private，Base2的改成public，编译器应该也让它通过。而这时，乙的程序的含义就被偷偷修改了，与前面的例子很相似吧？但因为编译器有自己的那条原则，所以①处的代码编译不了，乙必须明确的用
<pre>	d.Base1::doIt();
</pre>
<br>才行。这样，如果甲再做修改，这一行就会出错，乙就能及时跟进了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;其实，我认为这两个问题都属于&#8220;潜在的二义性&#8221;问题，照理说编译器应该一视同仁，给予相同的处理。可它为什么对第二种大嚷大叫，却对第一种不闻不问呢？原因还是在于，第一种情况下，C++没有给乙&#8220;明确指出传值还是传引用&#8221;的权力。
<h3>2. 不完备的C++类型系统</h3>
&nbsp;&nbsp;&nbsp;&nbsp;C++本身的类型系统庞大而复杂，而且还给了程序员创建与内置类型具有相同行为模式的自定义类型的能力，但它在引用类型上却有一些缺陷。从汇编的角度，我想大家都知道，引用和指针几乎是一模一样的。但指针有一个取指针（取地址）运算符&#8220;&amp;&#8221;和一个取值运算符&#8220;*&#8221;，用来在指针和它所指的对象之间作相互转换，而引用却没有，两个都没有。C++对此的解决办法是让引用类型必须在定义的时候赋初值，并且终生不能改变其所引用的对象。我们初学C++时就知道引用类型有此限制，但恐怕很少有人想过这是为什么。好了，看下面的代码：
<pre>	//假设sometype和整数是兼容类型
sometype m = 10, n = 11;
sometype&amp; r = m;
r = n;                       //①
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;如果没有那个限制，语句①就有二义性了。程序员是想把n的值赋给r（也就是m）呢，还是想让r成为n的引用呢？编译器那个愁啊，最后终于决定：引用类型必须在定义时赋初值[注1]，这里的语义只能是把n的值赋给r。不过这一限制使引用看起来像一个&#8220;劣等公民&#8221;，让整个C++类型系统显得稍微有些不和谐。
<h3>3. 增加一个&#8220;取引用&#8221;运算符又如何？</h3>
&nbsp;&nbsp;&nbsp;&nbsp;在第一节的讨论中，如果有一个&#8220;按引用传参&#8221;运算符[注2]，C++只需规定：对引用类型的参数必须使用按引用传参运算符，就不会有问题了（为了后文讨论方便，我们假设这个运算符用&#8220;@&#8221;表示）。因为，一旦甲把foo的参数改成了&#8220;int&amp;&#8221;，而乙在调用时没有用&#8220;foo( @m )&#8221;，这一行就是个编译错误，乙就必须进行修改了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;不过，我们似乎没有理由让它只用于函数调用，它也应该能用于程序的任何位置，就像取指针运算符一样。所以，它应该叫&#8220;取引用&#8221;运算符，而不是&#8220;按引用传参&#8221;运算符。更进一步，为了类型系统的完备，我们应该也有一个用于引用的&#8220;取值&#8221;运算符，为避免混淆，就叫它&#8220;解引用&#8221;运算符吧，其符号定位&#8220;$&#8221;。 <br>&nbsp;&nbsp;&nbsp;&nbsp;啊哈！大功告成！现在我宣布，一门全新的语言—C+++—诞生了。让我们看看这门语言激动人心的新特性吧。 <br>&nbsp;&nbsp;&nbsp;&nbsp;首先，第一节中的问题彻底成了历史，因为我们有了强大的&#8220;@&#8221;运算符。 <br>&nbsp;&nbsp;&nbsp;&nbsp;其次，引用类型定义时赋初值的限制没有了，因为语义可以通过&#8220;@&#8221;作明确的表达，如：
<pre>	int m = 10, n = 11;
int&amp; r;
r = @m;		//让r成为m的引用
r = n;		//把n的值赋给r
r = @n;		//让r成为n的引用
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;不过这些都是&#8220;@&#8221;的用处，&#8220;$&#8221;怎么用呢？没问题，接着看下面的例子：
<pre>	int m = 10;
int&amp; r1 = @m;
int n = $r1;		//①
int$ r2 = @n;
r2 = $r1; 		//② 把r1引用的值赋给r2
r2 = r1;		//让r2也去引用m</pre>
&nbsp;&nbsp;&nbsp;&nbsp;怎么样，还不错吧？不过语句①显得有点画蛇添足了，因为这里有没有&#8220;$&#8221;，语义都一样，只能是把r1引用的值赋给n。好，既然如此，那就再对我们的新语言进行一个小修改，规定&#8220;$&#8221;只用于语句②的情况，在①中不能用，也就是说由引用类型向被引用类型赋值不需要&#8220;$&#8221;。 <br>&nbsp;&nbsp;&nbsp;&nbsp;好像很完美了，但C++允许通过重载&#8220;[]&#8221;运算符来定义程序员自己的数组类，我们的C+++当然也要支持。而且，为了支持对数组元素的赋值，&#8220;[]&#8221;的返回值肯定应该是引用类型，所以我们可以写下面的代码：
<pre>	//array是程序员自定义的整形数组的实例
array[0] = 10;
int n = array[1];        //①
int&amp; r = @n;
array[3] = r;            //②</pre>
&nbsp;&nbsp;&nbsp;&nbsp;语句②好像有些不大对，array[3]是&#8220;int&amp;&#8221;类型，所以这一句的含义是让&#8220;array的&#8216;[]&#8217;运算符的返回值&#8221;去引用&#8220;r的引用对象&#8221;，而不是我们所要达到的把&#8220;r引用的值&#8221;赋给array[3]。当然，出现这一问题的原因是我们没有按C+++的语法规则去写代码，只要把它改成
<pre>	array[3] = $r;
</pre>
<br>就可以了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;可细想一下就又不对了，如果array是语言的内置数组类型，那么array[3]就是&#8220;int&#8221;类型，我们刚刚规定，这种情况不用&#8220;$&#8221;。而自定义类型的行为模式应该和内置类型保持一致，所以&#8230;&#8230;，我们还是把刚才那条规定废了吧，毕竟多写一个&#8220;$&#8221;不会带来什么问题，C+++是新生事物，在一些地方出现反复在所难免，大家说是吧？ <br>&nbsp;&nbsp;&nbsp;&nbsp;但编译器马上叫起来：&#8220;语句①有错误，缺少&#8216;$&#8217;运算符&#8221;。我们的编译器真不错，尽职尽责，它要不说我还真就忘了，按我们最新的语法规则，这里确实应该有一个&#8220;$&#8221;。但话还得说回来，如果array是内置数组类型，这个&#8220;$&#8221;是不应该加的，自定义类型的行为又何内置类型不一样了，这可怎么办呢？要不咱就再加条规定，让内置类型在这种情况下也加个&#8220;$&#8221;？ <br>&nbsp;&nbsp;&nbsp;&nbsp;好，说干就干，现在那段代码变成了：
<pre>	//array是程序员自定义的整形数组的实例
array[0] = 10;
int n = $array[1];        //①
int&amp; r = @n;
array[3] = $r;            //②</pre>
&nbsp;&nbsp;&nbsp;&nbsp;重新编译，编译器又叫起来：&#8220;语句②有二义性。&#8221;什么？二义性？刚才还好好的，加了一条规则怎么就有二义性了呢？仔细看一下，它还真没说错。如果array是内置类型，则array[1]是&#8220;int&#8221;型，所以语句①加&#8220;$&#8221;就意味着同类型变量之间赋值要加&#8220;$&#8221;。再来看②，array[3]是&#8220;int&amp;&#8221;，r也是&#8220;int&amp;&#8221;，所以它的含义又变回了让&#8220;array的&#8216;[]&#8217;运算符的返回值&#8221;去引用&#8220;r的引用对象&#8221;。幸亏聪明的编译器猜出了我们的真实意愿，报了个二义性错误，否则，这样的bug怎么找呀？ <br>&nbsp;&nbsp;&nbsp;&nbsp;看来，语法规则还得改。我又重新检查了前面的一系列问题，发现其根源都在于&#8220;引用类型可以相互赋值&#8221;，只要把它禁用，所有问题都将迎刃而解，而且这也让&#8220;$&#8221;在普通的表达式中失去存在的必要，从而使语法更简洁。终于，我们的代码又回到了最初的形式（注意：这里并没有规定引用必须在定义时赋初值，也没有规定引用的对象必须终生不变）：
<pre>	//array是程序员自定义的整形数组的实例
array[0] = 10;
int n = array[1];
int&amp; r = @n;
array[3] = r;		//①
</pre>
<br>&nbsp;&nbsp;&nbsp;&nbsp;因为引用之间不能赋值，所以语句①没有任何问题 <br>&nbsp;&nbsp;&nbsp;&nbsp;时间一天天过去，一切都很正常，直到有一天，程序员写出了下面的代码：
<pre>	//array是程序员自定义的整形数组的实例
array[0] = 10;
int&amp; r = @array[1];	//①
int n = array[2];
r = n;               	//②</pre>
&nbsp;&nbsp;&nbsp;&nbsp;编译器又抱怨了：&#8220;语句①有错误，类型不匹配&#8221;。说实话，我的心已经经受不了这种打击了，不过还是得硬着头皮看看到底出了什么事：又是自定义类型和内置类型行为模式不一致。由于array是程序员自己定义的，所以array[1]的类型是&#8220;int&amp;&#8221;，@array[1]的类型是&#8220;int&amp;&amp;&#8221;，但表达式的左面却是个&#8220;int&amp;&#8221;，难怪了（注意：这里是引用的初始化，而不是把&#8220;int&amp;&amp;&#8221;赋值给&#8220;int&amp;&#8221;，后者是&#8220;由引用类型向被引用类型赋值&#8221;，没有错误）。但如果array是内置的数组类型，该表达式又没有任何问题。这可如何是好呢？要不让内置类型在这种情况下也不用加&#8220;@&#8221;？别急，我得好好分析一下，我可不想重蹈覆辙了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;如果内置类型也不用加&#8220;@&#8221;，那将导致可以通过赋值语句直接让一个&#8220;int&amp;&#8221;型变量成为一个&#8220;int&#8221;型变量的引用，可这又会使语句②出现二义性错误，要修正这个错误，就必须规定引用型变量只能在定义时赋初值，但这个规定又让&#8220;@&#8221;像&#8220;$&#8221;一样在普通表达式中失去存在的价值。 <br>&nbsp;&nbsp;&nbsp;&nbsp;经过了一系列的调整，&#8220;@&#8221;和&#8220;$&#8221;的作用就只剩下在函数调用中指定参数类型了，但前面那么多的问题，使我对它们这唯一的用途也产生了怀疑。而且，残酷的事实也表明，无情的自定义类型并没有放过我最后的这根救命稻草，看下面的代码吧：
<pre>	void foo( int a );
void bar( int&amp; a );
foo( array[0] );		//array是内置数组类型
foo( $array[0] );		//array是自定义数组类型
bar( @array[0] );		//array是内置数组类型
bar( array[0] );		//array是自定义数组类型</pre>
自定义类型和内置类型的不同行为模式，让我的C+++之梦彻底成了泡影。
<h3>4. 结论</h3>
&nbsp;&nbsp;&nbsp;&nbsp;真没想到增加一个&#8220;取引用&#8221;运算符会引出这么多事来，不过这也说明了C++没有引入这个运算符的原因。写完这篇文章，突然觉得我们这个世界好像并不喜欢完美的事物，类型系统不完备的C++可以大行其道，类型系统完备的C+++却只能是空中楼阁，真是不可思议。
<h3>注释：</h3>
<strong>注1：</strong>实际上，如果sometype是函数类型的话，即使没有&#8220;定义时赋初值&#8221;的限制，语句①也没有二义性。据我所知，VC6就允许&#8220;函数的引用&#8221;不在定义时赋初值，但到VC7和VC7.1就不行了。 <br><strong>注2：</strong>C#就引入了这样一个运算符，也就是关键字&#8220;ref&#8221;，不过从我个人来讲，更希望它是一个符号而不是个单词。 <br><strong>注3：</strong>文章对C+++的讨论完全是胡思乱想，可能有很多地方考虑不周，甚至是错误的，欢迎大家批评指正。 <img src="http://blog.vckbase.com/localvar/aggbug/12085.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132747.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2005-09-14 11:47 <a href="http://www.cppblog.com/localvar/archive/2005/09/14/132747.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>又长见识了</title><link>http://www.cppblog.com/localvar/archive/2005/08/24/132748.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Wed, 24 Aug 2005 07:34:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2005/08/24/132748.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132748.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2005/08/24/132748.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132748.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132748.html</trackback:ping><description><![CDATA[下面的代码居然能编译通过<br>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;mymemcpy(&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;dest,&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;src,&nbsp;size_t&nbsp;count&nbsp;)<br><img id=Codehighlighter1_60_468_Open_Image onclick="this.style.display='none'; Codehighlighter1_60_468_Open_Text.style.display='none'; Codehighlighter1_60_468_Closed_Image.style.display='inline'; Codehighlighter1_60_468_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_60_468_Closed_Image onclick="this.style.display='none'; Codehighlighter1_60_468_Closed_Text.style.display='none'; Codehighlighter1_60_468_Open_Image.style.display='inline'; Codehighlighter1_60_468_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_60_468_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_60_468_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;d&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)dest;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;s&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">)src;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;n&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;(count&nbsp;</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">7</span><span style="COLOR: #000000">)&nbsp;</span><span style="COLOR: #000000">/</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;count&nbsp;&gt;&nbsp;0&nbsp;assumed</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif"></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">switch</span><span style="COLOR: #000000">(&nbsp;count&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">7</span><span style="COLOR: #000000">&nbsp;)<br><img id=Codehighlighter1_191_451_Open_Image onclick="this.style.display='none'; Codehighlighter1_191_451_Open_Text.style.display='none'; Codehighlighter1_191_451_Closed_Image.style.display='inline'; Codehighlighter1_191_451_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_191_451_Closed_Image onclick="this.style.display='none'; Codehighlighter1_191_451_Closed_Text.style.display='none'; Codehighlighter1_191_451_Open_Image.style.display='inline'; Codehighlighter1_191_451_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_191_451_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_191_451_Open_Text><span style="COLOR: #000000">{<br><img id=Codehighlighter1_206_431_Open_Image onclick="this.style.display='none'; Codehighlighter1_206_431_Open_Text.style.display='none'; Codehighlighter1_206_431_Closed_Image.style.display='inline'; Codehighlighter1_206_431_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_206_431_Closed_Image onclick="this.style.display='none'; Codehighlighter1_206_431_Closed_Text.style.display='none'; Codehighlighter1_206_431_Open_Image.style.display='inline'; Codehighlighter1_206_431_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">:&nbsp;&nbsp;</span><span style="COLOR: #0000ff">do</span><span style="COLOR: #000000">&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_206_431_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_206_431_Open_Text><span style="COLOR: #000000">{&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">7</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">6</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">4</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">case</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">d</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">s</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">while</span><span style="COLOR: #000000">&nbsp;(</span><span style="COLOR: #000000">--</span><span style="COLOR: #000000">n&nbsp;</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif">&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;dest;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span></div>
</div>
这种代码侧重于效率优化，但可读性实在不敢恭维。不过还是记录一下，以免以后再见到时不认识，被人笑话.<br><img src="http://blog.vckbase.com/localvar/aggbug/11412.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132748.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2005-08-24 15:34 <a href="http://www.cppblog.com/localvar/archive/2005/08/24/132748.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>reinterpret_cast和static_cast</title><link>http://www.cppblog.com/localvar/archive/2005/07/26/132751.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Tue, 26 Jul 2005 12:47:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2005/07/26/132751.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132751.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2005/07/26/132751.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132751.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132751.html</trackback:ping><description><![CDATA[最近写一个使用完成端口的应用时, 居然在最简单的类型转换上栽了一个跟头, 写出来与大家分享，以避免犯和我类似的错误。<br>为了能尽量统一的处理每一个io操作, 我定义了下面这个类:<br>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;CIoPacket&nbsp;:&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;OVERLAPPED<br><img id=Codehighlighter1_36_173_Open_Image onclick="this.style.display='none'; Codehighlighter1_36_173_Open_Text.style.display='none'; Codehighlighter1_36_173_Closed_Image.style.display='inline'; Codehighlighter1_36_173_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_36_173_Closed_Image onclick="this.style.display='none'; Codehighlighter1_36_173_Closed_Text.style.display='none'; Codehighlighter1_36_173_Open_Image.style.display='inline'; Codehighlighter1_36_173_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_36_173_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_36_173_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;OnIoComplete(&nbsp;ULONG_PTR&nbsp;key,&nbsp;DWORD&nbsp;dwBytes&nbsp;)&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;OnIoFailure(&nbsp;ULONG_PTR&nbsp;key,&nbsp;DWORD&nbsp;dwBytes&nbsp;)&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span></div>
</div>
其中的两个虚函数分别在io操作成功完成或失败后被调用. 它的派生类将用于记录每一次io的相关信息. 为了从网络上接收数据, 我有从它派生出了一个类:<br>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;CNetPkt&nbsp;:&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;CIoPacket<br><img id=Codehighlighter1_33_220_Open_Image onclick="this.style.display='none'; Codehighlighter1_33_220_Open_Text.style.display='none'; Codehighlighter1_33_220_Closed_Image.style.display='inline'; Codehighlighter1_33_220_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_33_220_Closed_Image onclick="this.style.display='none'; Codehighlighter1_33_220_Closed_Text.style.display='none'; Codehighlighter1_33_220_Open_Image.style.display='inline'; Codehighlighter1_33_220_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_33_220_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_33_220_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif"></span><span style="COLOR: #0000ff">protected</span><span style="COLOR: #000000">:<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;定义一些成员变量</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif"></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;OnIoComplete(&nbsp;ULONG_PTR&nbsp;key,&nbsp;DWORD&nbsp;dwBytes&nbsp;)<br><img id=Codehighlighter1_127_142_Open_Image onclick="this.style.display='none'; Codehighlighter1_127_142_Open_Text.style.display='none'; Codehighlighter1_127_142_Closed_Image.style.display='inline'; Codehighlighter1_127_142_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_127_142_Closed_Image onclick="this.style.display='none'; Codehighlighter1_127_142_Closed_Text.style.display='none'; Codehighlighter1_127_142_Open_Image.style.display='inline'; Codehighlighter1_127_142_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_127_142_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_127_142_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;执行一些操作</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif"></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">virtual</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;OnIoFailure(&nbsp;ULONG_PTR&nbsp;key,&nbsp;DWORD&nbsp;dwBytes&nbsp;)<br><img id=Codehighlighter1_203_218_Open_Image onclick="this.style.display='none'; Codehighlighter1_203_218_Open_Text.style.display='none'; Codehighlighter1_203_218_Closed_Image.style.display='inline'; Codehighlighter1_203_218_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_203_218_Closed_Image onclick="this.style.display='none'; Codehighlighter1_203_218_Closed_Text.style.display='none'; Codehighlighter1_203_218_Open_Image.style.display='inline'; Codehighlighter1_203_218_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedSubBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_203_218_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_203_218_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;执行一些操作</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif"></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif">}</span></span><span style="COLOR: #000000">;</span></div>
</div>
<br>下面的代码启动了一个接受数据的操作:<br>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">SOCKET&nbsp;sck;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;初始化sck并绑定到一个完成端口</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">CNetPkt</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">&nbsp;pPkt&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;CNetPkt();<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;设置pPkt的相关字段</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #000000">::WSARecv(&nbsp;sck,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">wsabuf,&nbsp;</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytes,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwFlags,&nbsp;pPkt,&nbsp;NULL&nbsp;);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;(0)<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000"><img src="http://www.cppblog.com/Images/dot.gif"></span></div>
</div>
下面是完成端口线程中的代码:<br>
<div style="BORDER-BOTTOM: windowtext 0.5pt solid; BORDER-LEFT: windowtext 0.5pt solid; PADDING-BOTTOM: 4px; PADDING-LEFT: 5.4pt; WIDTH: 98%; PADDING-RIGHT: 5.4pt; BACKGROUND: #e6e6e6; BORDER-TOP: windowtext 0.5pt solid; BORDER-RIGHT: windowtext 0.5pt solid; PADDING-TOP: 4px">
<div><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><span style="COLOR: #000000">DWORD&nbsp;dwBytes&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">ULONG_PTR&nbsp;key&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif">LPOVERLAPPED&nbsp;pol&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;NULL;<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(&nbsp;::GetQueuedCompletionStatus(g_hIocp,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">dwBytes,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">key,&nbsp;</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">pol,&nbsp;INFINITE)&nbsp;)<br><img id=Codehighlighter1_139_231_Open_Image onclick="this.style.display='none'; Codehighlighter1_139_231_Open_Text.style.display='none'; Codehighlighter1_139_231_Closed_Image.style.display='inline'; Codehighlighter1_139_231_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_139_231_Closed_Image onclick="this.style.display='none'; Codehighlighter1_139_231_Closed_Text.style.display='none'; Codehighlighter1_139_231_Open_Image.style.display='inline'; Codehighlighter1_139_231_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_139_231_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_139_231_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(&nbsp;key&nbsp;</span><span style="COLOR: #000000">!=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">&nbsp;)<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">CIoPacket</span><span style="COLOR: #000000">*&gt;</span><span style="COLOR: #000000">(pol)</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">OnIoComplete(&nbsp;key,&nbsp;dwBytes&nbsp;);&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;(1)</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif"></span><span style="COLOR: #000000">}</span></span><span style="COLOR: #000000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/None.gif"></span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000"><br><img id=Codehighlighter1_238_333_Open_Image onclick="this.style.display='none'; Codehighlighter1_238_333_Open_Text.style.display='none'; Codehighlighter1_238_333_Closed_Image.style.display='inline'; Codehighlighter1_238_333_Closed_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif"><img style="DISPLAY: none" id=Codehighlighter1_238_333_Closed_Image onclick="this.style.display='none'; Codehighlighter1_238_333_Closed_Text.style.display='none'; Codehighlighter1_238_333_Open_Image.style.display='inline'; Codehighlighter1_238_333_Open_Text.style.display='inline';" align=top src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif"></span><span style="BORDER-BOTTOM: #808080 1px solid; BORDER-LEFT: #808080 1px solid; BACKGROUND-COLOR: #ffffff; DISPLAY: none; BORDER-TOP: #808080 1px solid; BORDER-RIGHT: #808080 1px solid" id=Codehighlighter1_238_333_Closed_Text><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_238_333_Open_Text><span style="COLOR: #000000">{<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(&nbsp;pol&nbsp;</span><span style="COLOR: #000000">!=</span><span style="COLOR: #000000">&nbsp;NULL&nbsp;)<br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reinterpret_cast</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">CIoPacket</span><span style="COLOR: #000000">*&gt;</span><span style="COLOR: #000000">(pol)</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">OnIoFailure(&nbsp;key,&nbsp;dwBytes&nbsp;);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;(2)</span><span style="COLOR: #008000"><br><img align=top src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif"></span><span style="COLOR: #000000">}</span></span></div>
</div>
<br>&nbsp;&nbsp;&nbsp; 自我感觉实现的既灵活又漂亮还健壮、高效(嗯?!哪里来的西红柿?味道还不错!). 但是程序每次运行到(1)或(2)时就会出现非法操作, 令我百思不得其解. 首先仔细检查程序, 没发现错误; 又使出十八般调试功夫, 还是没有找到问题所在. 正在头大之时, 突然发现(1)处的pol和(0)处的pPkt的值并不一样, pol比pPkt大了4, 进一步通过反汇编发现(0)处实际传给WSARecv的就是((LPBYTE)pPkt)+4. 两个值不一样, 总出错也就不奇怪了.<br>&nbsp;&nbsp;&nbsp; 可编译器为什么要给指针加上4呢? 难道是编译器把CIoPacket的vfptr放到了OVERLAPPED的前面? 可是我记得vc应该是把它放在后面的呀(具体不敢确定了, 但好像VC6就是放在后面). 一番测试证实了我的猜测, vc7.1就是会把vfptr放到类结构的最前面, 该死的微软居然偷偷改了这么重要的编译细节. 但也不能光骂微软, 自己的错误也要检讨一下, 上面的程序中我应该用static_cast, 而不是reinterpret_cast, 因为static_cast能正确调整基类和派生类的指针, 而reinterpret_cast从汇编的角度看是什么也不干的. 如果vfptr放在后面, static_cast和reinterpret_cast的结果是一样的, 但当vfptr放在前面的时候它们就不同了. 由于C++标准没有规定vfptr的放置位置, 所以大家进行类型转换时一定要注意选择正确方式, 避免出现我这样的、隐蔽的可移植性问题.<img src="http://blog.vckbase.com/localvar/aggbug/10068.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132751.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2005-07-26 20:47 <a href="http://www.cppblog.com/localvar/archive/2005/07/26/132751.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++编译器如何实现异常处理</title><link>http://www.cppblog.com/localvar/archive/2005/07/21/132753.html</link><dc:creator>局部变量</dc:creator><author>局部变量</author><pubDate>Thu, 21 Jul 2005 03:17:00 GMT</pubDate><guid>http://www.cppblog.com/localvar/archive/2005/07/21/132753.html</guid><wfw:comment>http://www.cppblog.com/localvar/comments/132753.html</wfw:comment><comments>http://www.cppblog.com/localvar/archive/2005/07/21/132753.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/localvar/comments/commentRss/132753.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/localvar/services/trackbacks/132753.html</trackback:ping><description><![CDATA[<h2 align=center>C++编译器如何实现异常处理</h2>
<p align=center><strong>作者：</strong>Vishal Kochhar&nbsp;&nbsp; <a href="http://www.codeproject.com/cpp/exceptionhandler.asp">查看原文</a> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>翻译：</strong><a href="http://www.cppblog.com/localvar">局部变量</a></p>
<p align=left><strong>注：</strong>本文在网上已经有几个译本，但都不完整，所以我决定自己把它翻译过来。虽然力求信、雅、达，但鉴于这是我的第一次翻译经历，不足之处敬请谅解并指出。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;与传统语言相比，C++的一项革命性创新就是它支持异常处理。传统的错误处理方式经常满足不了要求，而异常处理则是一个极好的替代解决方案。它将正常代码和错误处理代码清晰的划分开来，程序变得非常干净并且容易维护。本文讨论了编译器如何实现异常处理。我将假定你已经熟悉异常处理的语法和机制。本文还提供了一个用于VC++的异常处理库，要用库中的处理程序替换掉VC++提供的那个，你只需要调用下面这个函数： <font color=blue>
<pre>install_my_handler();</font></pre>
之后，程序中的所有异常，从它们被抛出到堆栈展开（stack unwinding），再到调用catch块，最后到程序恢复正常运行，都将由我的异常处理库来管理。 <br>&nbsp;&nbsp;&nbsp;&nbsp;与其它C++特性一样，C++标准并没有规定编译器应该如何来实现异常处理。这意味着每一个编译器的提供商都可以用它们认为恰当的方式来实现它。下面我会描述一下VC++是怎么做的，但即使你使用其它的编译器或操作系统①，本文也应该会是一篇很好的学习材料。VC++的实现方式是以windows系统的结构化异常处理（SEH）②为基础的。
<h3>结构化异常处理—概述</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;在本文的讨论中，我认为异常或者是被明确的抛出的，或者是由于除零溢出、空指针访问等引起的。当它发生时会产生一个中断，接下来控制权就会传递到操作系统的手中。操作系统将调用异常处理程序，检查从异常发生位置开始的函数调用序列，进行堆栈展开和控制权转移。Windows定义了结构&#8220;EXCEPTION_REGISTRATION&#8221;，使我们能够向操作系统注册自己的异常处理程序。</p>
<font color=blue>
<pre>struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
DWORD handler;
}; </font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;注册时，只需要创建这样一个结构，然后把它的地址放到FS段偏移0的位置上去就行了。下面这句汇编代码演示了这一操作：</p>
<font color=blue>
<pre>mov FS:[0], exc_regp</font></pre>
<p>prev字段用于建立一个EXCEPTION_REGISTRATION结构的链表，每次注册新的EXCEPTION_REGISTRATION时，我们都要把原来注册的那个的地址存到prev中。 <br>&nbsp;&nbsp;&nbsp;&nbsp;那么，那个异常回调函数长什么样呢？在excpt.h中，windows定义了它的原形：</p>
<font color=blue>
<pre>EXCEPTION_DISPOSITION (*handler)(
_EXCEPTION_RECORD *ExcRecord,
void* EstablisherFrame,
_CONTEXT *ContextRecord,
void* DispatcherContext);  </font></pre>
<p>不要管它的参数和返回值，我们先来看一个简单的例子。下面的程序注册了一个异常处理程序，然后通过除以零产生了一个异常。异常处理程序捕获了它，打印了一条消息就完事大吉并退出了。</p>
<font color=blue>
<pre>#include &lt;iostream&gt;
#include &lt;windows.h&gt;
using std::cout;
using std::endl;
struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
DWORD handler;
};
EXCEPTION_DISPOSITION myHandler(
_EXCEPTION_RECORD *ExcRecord,
void * EstablisherFrame,
_CONTEXT *ContextRecord,
void * DispatcherContext)
{
cout &lt;&lt; "In the exception handler" &lt;&lt; endl;
cout &lt;&lt; "Just a demo. exiting..." &lt;&lt; endl;
exit(0);
return ExceptionContinueExecution; //不会运行到这
}
int  g_div = 0;
void bar()
{
//初始化一个EXCEPTION_REGISTRATION结构
EXCEPTION_REGISTRATION reg, *preg = &amp;reg;
reg.handler = (DWORD)myHandler;
//取得当前异常处理链的&#8220;头&#8221;
DWORD prev;
_asm
{
mov EAX, FS:[0]
mov prev, EAX
}
reg.prev = (EXCEPTION_REGISTRATION*) prev;
//注册！
_asm
{
mov EAX, preg
mov FS:[0], EAX
}
//产生一个异常
int  j = 10 / g_div;  //异常，除零溢出
}
int  main()
{
bar();
return 0;
}
/*-------输出-------------------
In the exception handler
Just a demo. exiting...
---------------------------------*/</font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;注意EXCEPTION_REGISTRATION必须定义在栈上，并且必须位于比上一个结点更低的内存地址上，windows对此有严格要求，达不到的话，它就会立刻终止进程。</p>
<h3>函数和堆栈</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;堆栈是用来保存局部对象的连续内存区。更明确的说，每个函数都有一个相关的栈桢（stack frame）来保存它所有的局部对象和表达式计算过程中用到的临时对象，至少理论上是这样的。但现实中，编译器经常会把一些对象放到寄存器中以便能以更快的速度访问。堆栈是一个处理器（CPU）层次的概念，为了操纵它，处理器提供了一些专用的寄存器和指令。 <br>&nbsp;&nbsp;&nbsp;&nbsp;图1是一个典型的堆栈，它示出了函数foo调用bar，bar又调用widget时的情景。请注意堆栈是向下增长的，这意味着新压入的项的地址低于原有项的地址。 <br><img hspace=0 alt=图1 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_1.gif" width=703 height=346> <br>&nbsp;&nbsp;&nbsp;&nbsp;通常编译器使用EBP寄存器来指示当前活动的栈桢。本例中，CPU正在运行widget，所以图中的EBP指向了widget的栈桢。编译器在编译时将所有局部对象解析成相对于栈桢指针（EBP）的固定偏移，函数则通过栈桢指针来间接访问局部对象。举个例子，典型的，widget访问它的局部变量时就是通过访问栈桢指针以下的、有着确定位置的几个字节来实现的，比如说EBP-24。 <br>&nbsp;&nbsp;&nbsp;&nbsp;上图中也画出了ESP寄存器，它叫栈指针，指向栈的最后一项。在本例中，ESP指着widget的栈桢的末尾，这也是下一个栈桢（如果它被创建的话）的开始位置。 <br>&nbsp;&nbsp;&nbsp;&nbsp;处理器支持两种类型的栈操作：压栈（push）和弹栈（pop）。比如，</p>
<font color=blue>
<pre>pop EAX</font></pre>
<p>的作用是从ESP所指的位置读出4字节放到EAX寄存器中，并把ESP加上（记住，栈是向下增长的）4（在32位处理器上）；类似的，</p>
<font color=blue>
<pre>push EBP</font></pre>
<p>的作用是把ESP减去4，然后将EBP的值放到ESP指向的位置中去。 <br>&nbsp;&nbsp;&nbsp;&nbsp;编译器编译一个函数时，会在它的开头添加一些代码来为其创建并初始化栈桢，这些代码被称为序言（prologue）；同样，它也会在函数的结尾处放上代码来清除栈桢，这些代码叫做尾声（epilogue）。 <br>&nbsp;&nbsp;&nbsp;&nbsp;一般情况下，序言是这样的：</p>
<font color=blue>
<pre>Push EBP     ; 把原来的栈桢指针保存到栈上
Mov EBP, ESP ; 激活新的栈桢
Sub ESP, 10  ; 减去一个数字，让ESP指向栈桢的末尾</font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;第一条指令把原来的栈桢指针EBP保存到栈上；第二条指令通过让EBP指向主调函数的EBP的保存位置来激活被调函数的栈桢；第三条指令把ESP减去了一个数字，这样ESP就指向了当前栈桢的末尾，而这个数字是函数要用到的所有局部对象和临时对象的大小。编译时，编译器知道函数的所有局部对象的类型和&#8220;体积&#8221;，所以，它能很容易的计算出栈桢的大小。 <br>&nbsp;&nbsp;&nbsp;&nbsp;尾声所做的正好和序言相反，它必须把当前栈桢从栈上清除掉：</p>
<font color=blue>
<pre>Mov ESP, EBP
Pop EBP      ; 激活主调函数的栈桢
Ret          ; 返回主调函数</font></pre>
<p>它让ESP指向主调函数的栈桢指针的保存位置（也就是被调函数的栈桢指针指向的位置），弹出EBP从而激活主调函数的栈桢，然后返回主调函数。 <br>&nbsp;&nbsp;&nbsp;&nbsp;一旦CPU遇到返回指令，它就要做以下两件事：把返回地址从栈中弹出，然后跳转到那个地址去。返回地址是主调函数执行call指令调用被调函数时自动压栈的。Call指令执行时，会先把紧随在它后面的那条指令的地址（被调函数的返回地址）压入栈中，然后跳转到被调函数的开始位置。图2更详细的描绘了运行时的堆栈。如图所示，主调函数把被调函数的参数也压进了堆栈，所以参数也是栈桢的一部分。函数返回后，主调函数需要移除这些参数，它通过把所有参数的总体积加到ESP上来达到目的，而这个体积可以在编译时知道： </p>
<font color=blue>
<pre>Add ESP, args_size</font></pre>
<p>当然，也可以把参数的总体积写在被调函数的返回指令的后面，让被调函数去移除参数，下面的指令就在返回主调函数前从栈中移去了24个字节：</p>
<font color=blue>
<pre>Ret 24</font></pre>
<p>取决于被调函数的调用约定（call convention），这两种方式每次只能用一个。你还要注意的是每个线程都有自己独立的堆栈。 <br><img alt=图2 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_2.gif"> </p>
<h3>C++和异常</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;回忆一下我在第一节中介绍的EXCEPTION_REGISTRATION结构，我们曾用它向操作系统注册了发生异常时要被调用的回调函数。VC++也是这么做的，不过它扩展了这个结构的语义，在它的后面添加了两个新字段：</p>
<font color=blue>
<pre>struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
DWORD handler;
int id;
DWORD ebp;
}; </font></pre>
&nbsp;&nbsp;&nbsp;&nbsp;VC++会为绝大部分函数③添加一个EXCEPTION_REGISTRATION类型的局部变量，它的最后一个字段（ebp）与栈桢指针指向的位置重叠。函数的序言创建这个结构并把它注册给操作系统，尾声则恢复主调函数的EXCEPTION_REGISTRATION。id字段的意义我将在下一节介绍。 <br>&nbsp;&nbsp;&nbsp;&nbsp;VC++编译函数时会为它生成两部分数据： <br>a）异常回调函数 <br>b）一个包含函数重要信息的数据结构，这些信息包括catch块、这些块的地址和这些块所关心的异常的类型等等。我把这个结构称为funcinfo，有关它的详细讨论也在下一节。 <br>&nbsp;&nbsp;&nbsp; 图3是考虑了异常处理之后的运行时堆栈。widget的异常回调函数位于由FS:[0]指向的异常处理链的开始位置（这是由widget的序言设置的）。异常处理程序把widget的funcinfo结构的地址交给函数__CxxFrameHandler，__CxxFrameHandler会检查这个结构看函数中有没有catch块对当前的异常感兴趣。如果没有的话，它就返回ExceptionContinueSearch给操作系统，于是操作系统会从异常处理链表中取得下一个结点，并调用它的异常处理程序（也就是调用当前函数的那个函数的异常处理程序）。 <br><img alt=图3 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_3.gif"> <br>&nbsp;&nbsp;&nbsp;&nbsp;这一过程将一直进行下去——直到处理程序找到一个能处理当前异常的catch块为止，这时它就不再返回操作系统了。但是在调用catch块之前（由于有funcinfo结构，所以知道catch块的入口，参见图3），必须进行堆栈展开，也就是清理掉当前函数的栈桢下面的所有其他的栈桢。这个操作稍微有点复杂，因为：异常处理程序必须找到异常发生时生存在这些栈桢上的所有局部对象，并依次调用它们的析构函数。后面我将对此进行详细介绍。 <br>&nbsp;&nbsp;&nbsp;&nbsp;异常处理程序把这项工作委托给了各个栈桢自己的异常处理程序。从FS:[0]指向的异常处理链的第一个结点开始，它依次调用每个结点的处理程序，告诉它堆栈正在展开。与之相呼应，这些处理程序会调用每个局部对象的析构函数，然后返回。此过程一直进行到与异常处理程序自身相对应的那个结点为止。 <br>&nbsp;&nbsp;&nbsp;&nbsp;由于catch块是函数的一部分，所以它使用的也是函数的栈桢。因此，在调用catch块之前，异常处理程序必须激活它所隶属的函数的栈桢。 <br>&nbsp;&nbsp;&nbsp;&nbsp;其次，每个catch块都只接受一个参数，其类型是它希望捕获的异常的类型。异常处理程序必须把异常对象本身或者是异常对象的引用拷贝到catch块的栈桢上，编译器在funcinfo中记录了相关信息，处理程序根据这些信息就能知道到哪去拷贝异常对象了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;拷贝完异常并激活栈桢后，处理程序将调用catch块。而catch块将把控制权下一步要转移到的地址返回来。请注意：虽然这时堆栈已经展开，栈桢也都被清除了，但它们占据的内存空间并没有被覆盖，所有的数据都还好好的待在栈上。这是因为异常处理程序仍在执行，象其他函数一样，它也需要栈来存放自己的局部对象，而其栈桢就位于发生异常的那个函数的栈桢的下面。catch块返回以后，异常处理程序需要&#8220;杀掉&#8221;异常对象。此后，它让ESP指向目标函数（控制权要转移到的那个函数）的栈桢的末尾——这样就把（包括它自己的在内的）所有栈桢都删除了，然后再跳转到catch块返回的那个地址去，就胜利的完成整个异常处理任务了。但它怎么知道目标函数的栈桢末尾在哪呢？事实上它没法知道，所以编译器把这个地址保存到了栈桢上（由前言来完成），如图3所示，栈桢指针EBP下面第16个字节就是。 <br>&nbsp;&nbsp;&nbsp;&nbsp;当然，catch块也可能抛出新异常，或者是将原来的异常重新抛出。处理程序必须对此有所准备。如果是抛出新异常，它必须杀掉原来的那个；而如果是重新抛出原来的异常，它必须能继续传播（propagate）这个异常。 <br>&nbsp;&nbsp;&nbsp;&nbsp;这里我要特别强调一点：由于每个线程有自己独立的堆栈，所以每个线程也都有自己独立的、由FS:[0]指向的EXCEPTION_REGISTRATION链。
<h3>C++和异常—2</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;图4是funcinfo的布局，注意这里的字段名可能与VC++编译器实际使用的不完全一致，而且我也只给出了和我们的讨论相关的字段。堆栈展开表（unwind table）的结构留到下节再讨论。 <br><img alt=图4 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_4.gif"> <br>&nbsp;&nbsp;&nbsp;&nbsp;异常处理程序在函数中查找catch块时，它首先要判断异常发生的位置是否在当前函数（发生异常的那个函数）的一个try块中。是则查找与此try块相关的catch块表，否则直接返回。 <br>&nbsp;&nbsp;&nbsp;&nbsp;先来看看它怎样找try块。编译时，编译器给每个try块都分配了start id和end id。通过funcinfo结构，异常处理程序可以访问这两个id，见图4。编译器为函数中的每个try块都生成了相关的数据结构。 <br>&nbsp;&nbsp;&nbsp;&nbsp;上一节中，我说过VC++给EXCEPTION_REGISTRATION结构加上了一个id字段。回忆一下图3，这个结构位于函数的栈桢上。异常发生时，处理程序读出这个值，看它是否在try块的两个id确定的区间[start id，end id]中。是的话，异常就发生在这个try块中；否则继续查看try块表中的下一个try块。 <br>&nbsp;&nbsp;&nbsp;&nbsp;谁负责更新id的值，它的值又应该是什么呢？原来，编译器会在函数的多个位置安插代码来更新id的值，以反应程序的实时运行状态。比如说，编译器会在进入try块的地方加上一条语句，把try块的start id写到栈桢上。 <br>&nbsp;&nbsp;&nbsp;&nbsp;找到try块后，处理程序就遍历与其关联的catch块表，看是否有对当前异常感兴趣的catch块。在try块发生嵌套时，异常将既源于内层try块，也源于外层try块。这种情况下，处理程序应该按先内后外的顺序查找catch块。但它其实没必要关心这些，因为，在try块表中，VC++总是把内层try块放在外层try块的前面。 <br>&nbsp;&nbsp;&nbsp;&nbsp;异常处理程序还有一个难题就是&#8220;如何根据catch块的相关数据结构判断这个catch块是否愿意处理当前异常&#8221;。这是通过比较异常的类型和catch块的参数的类型来完成的。例如下面这个程序：</p>
<font color=blue>
<pre>void foo()
{
try
{
throw E();
}
catch(H)
{
//.
}
}  </font></pre>
<p>如果H和E的类型完全相同的话，catch块就要捕获这个异常。这意味着处理程序必须在运行时进行类型比较，对C等语言来说，这是不可能的,因为它们无法在运行时得到对象的类型。C++则不同，它有了运行时类型识别（runtime type identification，RTTI），并提供了运行时类型比较的标准方法。C++在标准头文件<typeinfo>中定义了type_info类，它能在运行时代表一个类型。catch块数据结构的第二个字段（ptype_info，见图4）是一个指向type_info结构的指针，它在运行时就代表catch块的参数类型。type_info也重载了==运算符，能够指出两种类型是否完全相同。这样，异常处理程序只要比较（调用==运算符）catch块参数的type_info（可以通过catch块的相关数据结构来访问）和异常的type_info是否相同，就能知道catch块是不是愿意捕获当前异常了。 <br>&nbsp;&nbsp;&nbsp;&nbsp;catch块的参数类型可以通过funcinfo结构得到，但异常的type_info从哪来呢？当编译器碰到</p>
<font color=blue>
<pre>throw E();</font></pre>
<p>这条语句时，它会为异常生成一个excpt_info结构，如图5所示。还是要提醒你注意这里用的名字可能与VC++使用的不一致，而且仍然只有与我们的讨论相关的字段。从图中可以看出，异常的type_info可以通过excpt_info结构得到。由于异常处理程序需要拷贝异常对象（在调用catch块之前），也需要消除掉它（在调用catch块之后），所以编译器在这个结构中同时提供了异常的拷贝构造函数、大小和析构函数的信息。 <br><img alt=图5 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_5.gif"> <br>&nbsp;&nbsp;&nbsp;&nbsp;在catch块的参数是基类，而异常是派生类时，异常处理程序也应该调用catch块。然而，这种情况下，比较它们的type_info绝对是不相等，因为它们本来就不是相同的类型。而且，type_info类也没有提供任何其他函数或运算符来指出一个类是另一个类的基类。但异常处理程序还必须得去调用catch块！为了解决这个问题，编译器只能为处理程序提供更多的信息：如果异常是派生类，那么etypeinfo_table（通过excpt_info访问）将包含多个指向etype_info（扩展了type_info，这个名字是我启的）的指针，它们分别指向了各个基类的etype_info。这样，处理程序就可以把catch块的参数和所有这些type_info比较，只要有一个相同，就调用catch块。 <br>&nbsp;&nbsp;&nbsp;&nbsp;在结束这一部分之前，还有最后一个问题：异常处理程序是怎么知道异常和excpt_info结构的？下面我就要回答这个问题。 <br>&nbsp;&nbsp;&nbsp;&nbsp;VC++会把throw语句翻译成下面的样子： </p>
<font color=blue>
<pre>//throw E(); //编译器会为E生成excpt_info结构
E e = E(); //在栈上创建异常
_CxxThrowException(&amp;e, E_EXCPT_INFO_ADDR);</font></pre>
<p>__CxxThrowException会把控制权连带它的两个参数都交给操作系统（控制权转移是通过软件中断实现的，请参见RaiseException）。而操作系统，在为调用异常回调函数做准备时，会把这两个参数打包到一个_EXCEPTION_RECORD结构中。接着，它从EXCEPTION_REGISTRATION链表的头结点（由FS:[0]指向）开始，依次调用各节点的异常处理程序。而且，指向当前EXCEPTION_REGISTRATION结构的指针也会作为异常处理程序的第二个参数出现。前面已经说过，VC++中的每个函数都在栈上创建并注册了EXCEPTION_REGISTRATION结构。所以传递这个参数可以让处理程序知道很多重要信息，比如说：EXCEPTION_REGISTRATION的id字段（用于查找catch块）、函数的栈桢（用于清理栈桢）和EXCEPTION_REGISTRATION结点在异常链表中的位置（用于堆栈展开）等。第一个参数是指向_EXCEPTION_RECORD结构的指针，通过它可以找到异常和它的excpt_info结构。下面是excpt.h中定义的异常回调函数的原型：</p>
<font color=blue>
<pre>EXCEPTION_DISPOSITION (*handler)(
_EXCEPTION_RECORD* ExcRecord,
void* EstablisherFrame,
_CONTEXT *ContextRecord,
void* DispatcherContext);</font></pre>
<p>后两个参数和我们的讨论关系不大。函数的返回值是一个枚举类型（也在excpt.h中定义），我前面已经说过，如果处理程序找不到catch块，它就会向系统返回ExceptionContinueSearch，对本文而言，我们只要知道这一个返回值就行了。_EXCEPTION_RECORD结构是在winnt.h中定义的：</p>
<font color=blue>
<pre>struct _EXCEPTION_RECORD
{
DWORD ExceptionCode;
DWORD ExceptionFlags;
_EXCEPTION_RECORD* ExcRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[15];
}EXCEPTION_RECORD; </font></pre>
&nbsp;&nbsp;&nbsp;&nbsp;ExceptionInformation数组中元素的个数和类型取决于ExceptionCode字段。如果是C++异常（异常代码是0xe06d7363，源于throw语句），那么数组中将包含指向异常和excpt_info结构的指针；如果是其他异常，那数组中基本上就不会有什么内容，这些异常包括除零溢出、访问违例等，你可以在winnt.h中找到它们的异常代码。 <br>&nbsp;&nbsp;&nbsp;&nbsp;ExceptionFlags字段用于告诉异常处理程序应该采取什么操作。如果它是EH_UNWINDING（见Except.inc），那是说堆栈正在展开，这时，处理程序要清理栈桢，然后返回。否则处理程序应该在函数中查找catch块并调用它。清理栈桢意味着必须找到异常发生时生存在栈桢上的所有局部对象,并调用其析构函数，下一节我们将就此进行详细讨论。
<h3>清理栈桢</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;C++标准明确指出：堆栈展开工作必须调用异常发生时所有生存的局部对象的析构函数。如下面的代码：</p>
<font color=blue>
<pre>int g_i = 0;
void foo()
{
T o1, o2;
{
T o3;
}
10/g_i; //这里会发生异常
T o4;
//...
} </font></pre>
<p>foo有o1、o2、o3、o4四个局部对象，但异常发生时，o3已经&#8220;死亡&#8221;，o4还未&#8220;出生&#8221;，所以异常处理程序应该只调用o1和o2的析构函数。&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;前面已经说过，编译器会在函数的很多地方安插代码来记录当前的运行状态。实际上，编译器在函数中设置了一些关键区域，并为它们分配了id，进入关键区域时要记录它的id，退出时恢复前一个id。try块就是一个例子,其id就是start id。所以，在try块的入口，编译器会把它的start id记到栈桢上去。局部对象从创建到销毁也确定了一个关键区域，或者，换句话说，编译器给每个局部对象分配了唯一的id，例如下面的程序： </p>
<font color=blue>
<pre>void foo()
{
T t1;
//.
} </font></pre>
<p>编译器会在t1的定义后面（也就是t1创建以后）,把它的id写到栈桢上：</p>
<font color=blue>
<pre>void foo()
{
T t1;
_id = t1_id; //编译器插入的语句
//.
} </font></pre>
<p>上面的_id是编译器偷偷创建的局部变量，它的位置与EXCEPTION_REGISTRATION的id字段重叠。类似的，在调用对象的析构函数前，编译器会恢复前一个关键区域的id。 <br>&nbsp;&nbsp;&nbsp;&nbsp;清理栈桢时，异常处理程序读出id的值（通过EXCEPTION_REGISTRATION结构的id字段或栈桢指针EBP下面的4个字节来访问）。这个id可以表明，函数在运行到与它相关联的那个点之前没有发生异常。所有在这一点之前定义的对象都已初始化，应该调用这些对象中的一部分或全部对象的析构函数。请注意某些对象是属于子块（如前面代码中的o3）的，发生异常时可能已经销毁了，不应该调用它们的析构函数。 <br>&nbsp;&nbsp;&nbsp;&nbsp;编译器还为函数生成了另一个数据结构——堆栈展开表（unwindtable，我启的名字），它是一个unwind结构的数组，可通过funcinfo来访问，如图4所示。函数的每个关键区域都有一个unwind结构，这些结构在展开表中出现的次序和它们所对应的区域在函数中的出现次序完全相同。一般unwind结构也会关联一个对象（别忘了，每个对象的定义都开辟了关键区域，并有id与其对应），它里面有如何销毁这个对象的信息。每当编译器碰到对象定义，它就生成一小段代码，这段代码知道对象在栈桢上的地址（就是它相对于栈桢指针的偏移），并能销毁它。unwind结构中有一个字段用于保存这段代码的入口地址： </p>
<font color=blue>
<pre>typedef void (*CLEANUP_FUNC)();
struct unwind
{
int prev;
CLEANUP_FUNC cf;
}; </font></pre>
<p>try块对应的unwind结构的cf字段是空值NULL，因为没有与它对应的对象，所以也没有东西需要它去销毁。通过prev字段，这些unwind结构也形成了一个链表。异常处理程序清理栈桢时，会读取当前的id值，以它为索引取得展开表中对应的项，并调用其第二个字段指向的清理代码，这样，那个与之关联的对象就被销毁了。然后，处理程序将以当前unwind结构的prev字段为索引，继续在展开表中找下一个unwind结构，调用其清理代码。这一过程将一直重复，直到链表的结尾（prev的值是-1）。图6画出了本节开始时提到的那段代码的堆栈展开表。 <br><img alt=图6 align=middle src="http://www.cppblog.com/images/vckbase_com/localvar/709/o_6.gif"> <br>&nbsp;&nbsp;&nbsp;&nbsp;现在把new运算符也加进来，对于下面的代码： </p>
<font color=blue>
<pre>T* p = new T();</font></pre>
系统会首先为T分配内存，然后调用它的构造函数。所以，如果构造函数抛出了异常，系统就必须释放这些内存。因此，动态创建那些拥有&#8220;有为的构造函数&#8221;的类型时，VC++也为new运算符分配了id，并且堆栈展开表中也有与其对应的项，其清理代码将释放分配的内存空间。调用构造函数前，编译器把new运算符的id存到EXCEPTION_REGISTRATION结构中，构造函数顺利返回后，它再把id恢复成原来的值。 <br>&nbsp;&nbsp;&nbsp;&nbsp;更进一步说，构造函数抛出异常时，对象可能刚刚构造了一部分，如果它有子成员对象或子基类对象，并且发生异常时它们中的一部分已经构造完成的话，就必须调用这些对象的析构函数。和普通函数一样，编译器也给构造函数生成了相关的数据来帮助完成这个任务。 <br>&nbsp;&nbsp;&nbsp;&nbsp;展开堆栈时，异常处理程序调用的是用户定义的析构函数，这一点你必须注意，因为它也有可能抛出异常！C++标准规定堆栈展开过程中，析构函数不能抛出异常，否则系统将调用std::terminate。
<h3>实现</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;本节我们讨论其他三个有待详细解释的问题： <br>a)&nbsp;如何安装异常处理程序 <br>b)&nbsp;catch块重新抛出异常或抛出新异常时应该如何处理 <br>c)&nbsp;如何对所有线程提供异常处理支持 <br>&nbsp;&nbsp;&nbsp;&nbsp;随同本文，有一个演示项目，查看其中的readme.txt文件可以得到一些编译方面的帮助①。 <br>&nbsp;&nbsp;&nbsp;&nbsp;第一项任务是安装异常处理程序，也就是把VC++的处理程序替换掉。从前面的讨论中，我们已经清楚地知道__CxxFrameHandler函数是VC++所有异常处理工作的入口。编译器为每个函数都生成一段代码，它们在发生异常时被调用，把相应的funcinfo结构的指针交给__CxxFrameHandler。 <br>&nbsp;&nbsp;&nbsp;&nbsp;install_my_handler()函数会改写__CxxFrameHandler的入口处的代码，让程序跳转到my_exc_handler()函数。不过，__CxxFrameHandler位于只读的内存页，对它的任何写操作都会导致访问违例，所以必须首先用VirtualProtectEx把该内存页的保护方式改成可读写，等改写完毕后，再改回只读。写入的数据是一个jmp_instr结构。 </p>
<font color=blue>
<pre>//install_my_handler.cpp
#include &lt;windows.h&gt;
#include "install_my_handler.h"
//C++默认的异常处理程序
extern "C"
EXCEPTION_DISPOSITION __CxxFrameHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
void* EstablisherFrame,
struct _CONTEXT* ContextRecord,
void* DispatcherContext
);
namespace
{
char cpp_handler_instructions[5];
bool saved_handler_instructions = false;
}
namespace my_handler
{
//我的异常处理程序 EXCEPTION_DISPOSITION
my_exc_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext
)  throw();
#pragma pack(push, 1)
struct jmp_instr
{
unsigned char jmp;
DWORD offset;
};
#pragma pack(pop)
bool WriteMemory(void* loc, void* buffer, int size)
{
HANDLE hProcess = GetCurrentProcess();
//把包含内存范围[loc，loc+size]的页面的保护方式改成可读写
DWORD old_protection;
BOOL ret = VirtualProtectEx(hProcess, loc, size, PAGE_READWRITE, &amp;old_protection);
if(ret == FALSE)
return false;
ret = WriteProcessMemory(hProcess, loc, buffer, size, NULL);
//恢复原来的保护方式
DWORD o2;
VirtualProtectEx(hProcess, loc, size, old_protection, &amp;o2);
return (ret == TRUE);
}
bool ReadMemory(void* loc, void* buffer, DWORD size)
{
HANDLE hProcess = GetCurrentProcess();
DWORD bytes_read = 0;
BOOL ret = ReadProcessMemory(hProcess, loc, buffer, size, &amp;bytes_read);
return (ret == TRUE &amp;&amp; bytes_read == size);
}
bool install_my_handler()
{
void* my_hdlr = my_exc_handler; void* cpp_hdlr = __CxxFrameHandler;
jmp_instr jmp_my_hdlr;
jmp_my_hdlr.jmp = 0xE9;
//从__CxxFrameHandler+5开始计算偏移，因为jmp指令长5字节
jmp_my_hdlr.offset = reinterpret_cast&lt;char*&gt;(my_hdlr) - (reinterpret_cast&lt;char*&gt;(cpp_hdlr) + 5);
if(!saved_handler_instructions)
{
if(!ReadMemory(cpp_hdlr, cpp_handler_instructions, sizeof(cpp_handler_instructions)))
return false;
saved_handler_instructions = true;
}
return WriteMemory(cpp_hdlr, &amp;jmp_my_hdlr, sizeof(jmp_my_hdlr));
}
bool restore_cpp_handler()
{
if(!saved_handler_instructions)
return false;
else
{
void* loc = __CxxFrameHandler;
return WriteMemory(loc, cpp_handler_instructions, sizeof(cpp_handler_instructions));
}
}
}</font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;编译指令#pragma pack(push, 1)告诉编译器不要在jmp_instr结构中填充任何用于对齐的空间。没有这条指令，jmp_instr的大小将是8字节，而我们需要它是5字节。 <br>&nbsp;&nbsp;&nbsp;&nbsp;现在重新回到异常处理这个主题上来。调用catch块时，它可能重新抛出异常或抛出新异常。前一种情况下，异常处理程序必须继续传播（propagate）当前异常；后一种情况下，它需要在继续之前销毁原来的异常。此时，处理程序要面对两个难题：&#8220;如何知道异常是源于catch块还是程序的其他部分&#8221;和&#8220;如何跟踪原来的异常&#8221;。我的解决方法是：在调用catch块之前，把当前异常保存在exception_storage对象中，并注册一个专用于catch块的异常处理程序——catch_block_protector。调用get_exception_storage()函数，就能得到exception_storage对象： </p>
<font color=blue>
<pre>exception_storage* p = get_exception_storage();
p-&gt;set(pexc, pexc_info);
注册 catch_block_protector;
调用catch块; //....</font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;这样，当catch块（重新）抛出异常时，程序将会执行catch_block_protector。如果是抛出了新异常，这个函数可以从exception_storage对象中分离出前一个异常并销毁它；如果是重新抛出原来的异常（可以通过ExceptionInformation数组的前两个元素知道是新异常还是旧异常，后一种情况下着两个元素都是0，参见下面的代码），就通过拷贝ExceptionInformation数组来继续传播它。下面的代码就是catch_block_protector()函数的实现。</p>
<font color=blue>
<pre>//-------------------------------------------------------------------
// 如果这个处理程序被调用了，可以断定是catch块（重新）抛出了异常。
// 异常处理程序（my_handler）在调用catch块之前注册了它。其任务是判断
// catch块抛出了新异常还是重新抛出了原来的异常，并采取相应的操作。
// 在前一种情况下，它需要销毁传递给catch块的前一个异常对象；在后一种
// 情况下，它必须找到原来的异常并将其保存到ExceptionRecord中供异常
// 处理程序使用。
//-------------------------------------------------------------------
EXCEPTION_DISPOSITION catch_block_protector(
_EXCEPTION_RECORD* ExceptionRecord,
void* EstablisherFrame,
struct _CONTEXT *ContextRecord,
void* DispatcherContext
) throw ()
{
EXCEPTION_REGISTRATION *pFrame;
pFrame= reinterpret_cast&lt;EXCEPTION_REGISTRATION*&gt;(EstablisherFrame);
if(!(ExceptionRecord-&gt;ExceptionFlags &amp; (_EXCEPTION_UNWINDING | _EXCEPTION_EXIT_UNWIND)))
{
void *pcur_exc = 0, *pprev_exc = 0;
const excpt_info *pexc_info = 0, *pprev_excinfo = 0;
exception_storage* p = get_exception_storage();
pprev_exc = p-&gt;get_exception();
pprev_excinfo = p-&gt;get_exception_info();
p-&gt;set(0, 0);
bool cpp_exc = ExceptionRecord-&gt;ExceptionCode == MS_CPP_EXC;
get_exception(ExceptionRecord, &amp;pcur_exc);
get_excpt_info(ExceptionRecord, &amp;pexc_info);
if(cpp_exc &amp;&amp; 0 == pcur_exc &amp;&amp; 0 == pexc_info) //重新抛出
{
ExceptionRecord-&gt;ExceptionInformation[1] = reinterpret_cast&lt;DWORD&gt;(pprev_exc);
ExceptionRecord-&gt;ExceptionInformation[2] = reinterpret_cast&lt;DWORD&gt;(pprev_excinfo);
}
else
{
exception_helper::destroy(pprev_exc, pprev_excinfo);
}
}
return ExceptionContinueSearch;
}</font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;下面是get_exception_storage()函数的一个实现：</p>
<font color=blue>
<pre>exception_storage* get_exception_storage()
{
static exception_storage es;
return &amp;es;
} </font></pre>
<p>&nbsp;&nbsp;&nbsp;&nbsp;在单线程程序中，这是一个完美的实现。但在多线程中，这就是个灾难了，想象一下多个线程访问它，并把异常对象保存在里面的情景吧。由于每个线程都有自己的堆栈和异常处理链，我们需要一个线程安全的get_exception_storage实现：每个线程都有自己单独的exception_storage，它在线程启动时被创建，并在结束时被销毁。Windows提供的线程局部存储（thread local storage，TLS）可以满足这个要求，它能让每个线程通过一个全局键值来访问为这个线程所私有的对象副本，这是通过TlsGetValue()和TlsSetValue这两个API来完成的。 <br>&nbsp;&nbsp;&nbsp;&nbsp;Excptstorage.cpp中给出了get_exception_storage()函数的实现。它会被编译成动态链接库，因为我们可以籍此知道线程的创建和退出——系统在这两种情况下都会调用所有（当前进程加载的）dll的DllMain()函数，这让我们有机会创建特定于线程的数据，也就是exception_storage对象。</p>
<font color=blue>
<pre>//excptstorage.cpp
#include "excptstorage.h"
#include &lt;windows.h&gt;
namespace
{
DWORD dwstorage;
}
namespace my_handler
{
__declspec(dllexport) exception_storage* get_exception_storage() throw ()
{
void * p = TlsGetValue(dwstorage);
return reinterpret_cast &lt;exception_storage*&gt;(p);
}
}
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
using my_handler::exception_storage;
exception_storage *p;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//主线程（第一个线程）不会收到DLL_THREAD_ATTACH通知，所以，
//与其相关的操作也放在这了
dwstorage = TlsAlloc();
if (-1 == dwstorage)
return FALSE;
p = new exception_storage();
TlsSetValue(dwstorage, p);
break ;
case DLL_THREAD_ATTACH:
p = new exception_storage();
TlsSetValue(dwstorage, p);
break;
case DLL_THREAD_DETACH:
p = my_handler::get_exception_storage();
delete p;
break ;
case DLL_PROCESS_DETACH:
p = my_handler::get_exception_storage();
delete p;
break ;
}
return TRUE;
}  </font></pre>
<h3>结论</h3>
&nbsp;&nbsp;&nbsp;&nbsp;综上所述，异常处理是在操作系统的协助下，由C++编译器和运行时异常处理库共同完成的。
<h3>注释和参考资料</h3>
&nbsp;&nbsp;&nbsp;&nbsp;① 本文写作期间，微软发布了Visual Studio 7.0。本文的异常处理库主要是在运行于奔腾处理器的windows2000上使用VC++6.0编译和测试的。但我也在VC++5.0和VC++7.0 beta版上测试过。6.0和7.0之间有一些差别，6.0先把异常（或其引用）拷贝到catch块的栈桢上，然后在调用catch块之前进行堆栈展开；7.0则先进行堆栈展开。在这方面，我的库代码的行为比较接近6.0版。 <br>&nbsp;&nbsp;&nbsp;&nbsp;② 参见Matt Pietrek发表在MSDN上的文章《structured exception handling》。 <br>&nbsp;&nbsp;&nbsp;&nbsp;③ 如果一个函数既不含try块，也没有定义任何具有&#8220;有为的析构函数&#8221;的对象，那么编译器将不为它生成用于异常处理的数据。<img src="http://blog.vckbase.com/localvar/aggbug/9890.html" width=1 height=1> 
<img src ="http://www.cppblog.com/localvar/aggbug/132753.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/localvar/" target="_blank">局部变量</a> 2005-07-21 11:17 <a href="http://www.cppblog.com/localvar/archive/2005/07/21/132753.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>