<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>林健的BLOG &#187; 计算机科学与编程</title>
	<atom:link href="http://blog.linjian.org/articles/category/cs-programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.linjian.org</link>
	<description>有容乃大，无欲则刚</description>
	<lastBuildDate>Fri, 10 Sep 2010 13:36:12 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=abc</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/><atom:link rel="hub" href="http://superfeedr.com/hubbub"/>		<item>
		<title>一道 C 语言指针访存题目的引申</title>
		<link>http://blog.linjian.org/articles/c-pointer-alignment-access/</link>
		<comments>http://blog.linjian.org/articles/c-pointer-alignment-access/#comments</comments>
		<pubDate>Sun, 08 Nov 2009 05:41:48 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[gcc]]></category>
		<category><![CDATA[实验]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=406</guid>
		<description><![CDATA[　　毕业生求职的时节，非毕业生接触到各种面试、笔试题目的几率也会相应地增加。下面请看一道经典的 C 语言指针访存题目，稍有些经验的朋友应该很快可以看出这个题目考查的是字节序、内存布局等知识点。然后在大脑中略排列一下，就能够给出答案（2000000）。 #include &#60;stdio.h&#62; &#160; int&#160;main() { &#160; &#160; int&#160;a[5] = {1, 2, 3, 4, 5}; &#160; &#160; int *pa = (int)(&#38;a) + 1; &#160; &#160; printf(&#34;%x\n&#34;, *pa); &#160; &#160; return&#160;0; } 　　不过，这个答案是否绝对正确，还要看题目所处的上下文了。如果题目明确说是在常见的 32 位 x86 平台上运行，那就无可厚非；但如果没有指明机器架构，那就要小心一点了，也许命题者真想考查一下求职者对非 x86 平台的了解程度呢。如果考虑机器架构，这个题目应当如何作答呢？粗想一下，我们需要考虑的是字长、字节序和对齐（alignment）访问规则。不过真要做实验看看，会发现这里面还是有一些花样的。如果没有实际经验，只凭教条加推测，很可能想不到其它平台上的一些细节之处。 　　我们换用一段信息量更丰富的程序来进行后续的实验。在不同的平台上，均使用未加特殊参数的 gcc 来编译这段程序—— #include &#60;stdio.h&#62; &#160; int&#160;main() { &#160; &#160; int&#160;x; &#160; &#160; int&#160;a[5] = [...]]]></description>
			<content:encoded><![CDATA[<p>　　毕业生求职的时节，非毕业生接触到各种面试、笔试题目的几率也会相应地增加。下面请看一道经典的 C 语言指针访存题目，稍有些经验的朋友应该很快可以看出这个题目考查的是字节序、内存布局等知识点。然后在大脑中略排列一下，就能够给出答案（2000000）。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline"><span class="hl-prepro">#include </span><span style="color: #8b0000;">&lt;</span><span style="color: Red;">stdio.h</span><span style="color: #8b0000;">&gt;</span><span class="hl-prepro"></span></li>
<li><span style="color: Gray;">&nbsp;</span></li>
<li><span class="hl-types">int</span><span style="color: Gray;">&nbsp;</span><span style="color: Blue;">main</span><span style="color: Olive;">()</span></li>
<li><span style="color: Olive;">{</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span class="hl-types">int</span><span style="color: Gray;">&nbsp;</span><span style="color: Blue;">a</span><span style="color: Olive;">[</span><span style="color: Maroon;">5</span><span style="color: Olive;">]</span><span style="color: Gray;"> = </span><span style="color: Olive;">{</span><span style="color: Maroon;">1</span><span style="color: Gray;">, </span><span style="color: Maroon;">2</span><span style="color: Gray;">, </span><span style="color: Maroon;">3</span><span style="color: Gray;">, </span><span style="color: Maroon;">4</span><span style="color: Gray;">, </span><span style="color: Maroon;">5</span><span style="color: Olive;">}</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span class="hl-types">int</span><span style="color: Gray;"> *</span><span style="color: Blue;">pa</span><span style="color: Gray;"> = </span><span style="color: Olive;">(</span><span class="hl-types">int</span><span style="color: Olive;">)(</span><span style="color: Gray;">&amp;</span><span style="color: Blue;">a</span><span style="color: Olive;">)</span><span style="color: Gray;"> + </span><span style="color: Maroon;">1</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Blue;">printf</span><span style="color: Olive;">(</span><span style="color: #8b0000;">&quot;</span><span style="color: Red;">%x</span><span style="color: Navy;">\</span><span style="color: Red;">n</span><span style="color: #8b0000;">&quot;</span><span style="color: Gray;">, *</span><span style="color: Blue;">pa</span><span style="color: Olive;">)</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Green;">return</span><span style="color: Gray;">&nbsp;</span><span style="color: Maroon;">0</span><span style="color: Gray;">;</span></li>
<li><span style="color: Olive;">}</span></li></ol></div>
<p>　　不过，这个答案是否绝对正确，还要看题目所处的上下文了。如果题目明确说是在常见的 32 位 x86 平台上运行，那就无可厚非；但如果没有指明机器架构，那就要小心一点了，也许命题者真想考查一下求职者对非 x86 平台的了解程度呢。如果考虑机器架构，这个题目应当如何作答呢？粗想一下，我们需要考虑的是字长、字节序和对齐（alignment）访问规则。不过真要做实验看看，会发现这里面还是有一些花样的。如果没有实际经验，只凭教条加推测，很可能想不到其它平台上的一些细节之处。<br />
　　我们换用一段信息量更丰富的程序来进行后续的实验。在不同的平台上，均使用未加特殊参数的 gcc 来编译这段程序——</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline"><span class="hl-prepro">#include </span><span style="color: #8b0000;">&lt;</span><span style="color: Red;">stdio.h</span><span style="color: #8b0000;">&gt;</span><span class="hl-prepro"></span></li>
<li><span style="color: Gray;">&nbsp;</span></li>
<li><span class="hl-types">int</span><span style="color: Gray;">&nbsp;</span><span style="color: Blue;">main</span><span style="color: Olive;">()</span></li>
<li><span style="color: Olive;">{</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span class="hl-types">int</span><span style="color: Gray;">&nbsp;</span><span style="color: Blue;">x</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span class="hl-types">int</span><span style="color: Gray;">&nbsp;</span><span style="color: Blue;">a</span><span style="color: Olive;">[</span><span style="color: Maroon;">5</span><span style="color: Olive;">]</span><span style="color: Gray;"> = </span><span style="color: Olive;">{</span><span style="color: Maroon;">0x11121314</span><span style="color: Gray;">, </span><span style="color: Maroon;">0x21222324</span><span style="color: Gray;">, </span><span style="color: Maroon;">0x31323334</span><span style="color: Gray;">, </span><span style="color: Maroon;">0x41424344</span><span style="color: Gray;">, </span><span style="color: Maroon;">0x51525354</span><span style="color: Olive;">}</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Green;">for</span><span style="color: Gray;">&nbsp;</span><span style="color: Olive;">(</span><span style="color: Blue;">x</span><span style="color: Gray;"> = </span><span style="color: Maroon;">0</span><span style="color: Gray;">; </span><span style="color: Blue;">x</span><span style="color: Gray;"> &lt; </span><span style="color: Maroon;">20</span><span style="color: Gray;">; </span><span style="color: Blue;">x</span><span style="color: Gray;">++</span><span style="color: Olive;">)</span><span style="color: Gray;"> </span><span style="color: Olive;">{</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; &nbsp; &nbsp; </span><span style="color: Blue;">printf</span><span style="color: Olive;">(</span><span style="color: #8b0000;">&quot;</span><span style="color: Red;">%02x </span><span style="color: #8b0000;">&quot;</span><span style="color: Gray;">, *</span><span style="color: Olive;">(</span><span class="hl-types">char</span><span style="color: Gray;"> *</span><span style="color: Olive;">)((</span><span class="hl-types">int</span><span style="color: Olive;">)(</span><span style="color: Gray;">&amp;</span><span style="color: Blue;">a</span><span style="color: Olive;">)</span><span style="color: Gray;"> + </span><span style="color: Blue;">x</span><span style="color: Olive;">))</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Olive;">}</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Blue;">printf</span><span style="color: Olive;">(</span><span style="color: #8b0000;">&quot;</span><span style="color: Navy;">\</span><span style="color: Red;">n</span><span style="color: #8b0000;">&quot;</span><span style="color: Olive;">)</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Green;">for</span><span style="color: Gray;">&nbsp;</span><span style="color: Olive;">(</span><span style="color: Blue;">x</span><span style="color: Gray;"> = </span><span style="color: Maroon;">0</span><span style="color: Gray;">; </span><span style="color: Blue;">x</span><span style="color: Gray;"> &lt; </span><span style="color: Maroon;">8</span><span style="color: Gray;">; </span><span style="color: Blue;">x</span><span style="color: Gray;">++</span><span style="color: Olive;">)</span><span style="color: Gray;"> </span><span style="color: Olive;">{</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; &nbsp; &nbsp; </span><span style="color: Blue;">printf</span><span style="color: Olive;">(</span><span style="color: #8b0000;">&quot;</span><span style="color: Red;">%08x </span><span style="color: #8b0000;">&quot;</span><span style="color: Gray;">, *</span><span style="color: Olive;">(</span><span class="hl-types">int</span><span style="color: Gray;"> *</span><span style="color: Olive;">)((</span><span class="hl-types">int</span><span style="color: Olive;">)(</span><span style="color: Gray;">&amp;</span><span style="color: Blue;">a</span><span style="color: Olive;">)</span><span style="color: Gray;"> + </span><span style="color: Blue;">x</span><span style="color: Olive;">))</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Olive;">}</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Blue;">printf</span><span style="color: Olive;">(</span><span style="color: #8b0000;">&quot;</span><span style="color: Navy;">\</span><span style="color: Red;">n</span><span style="color: #8b0000;">&quot;</span><span style="color: Olive;">)</span><span style="color: Gray;">;</span></li>
<li><span style="color: Gray;">&nbsp; &nbsp; </span><span style="color: Green;">return</span><span style="color: Gray;">&nbsp;</span><span style="color: Maroon;">0</span><span style="color: Gray;">;</span></li>
<li><span style="color: Olive;">}</span></li></ol></div>
<p>　　在 32 位 x86 下的结果不需要多解释。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>Linux ubuntu 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux</li>
<li>./a.out </li>
<li>14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51 </li>
<li>11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421</li></ol></div>
<p>　　而在 64 位的 x86_64 下，由于 8 字节的指针被截断到了 4 字节的整型长度，故会引发段错误。同样的情况出现在 64 位的 Alpha 机器下。解决办法自然是把运算地址时的 int 修改成 long 或某种显式的 64 位类型。修改后的结果应该与 32 位 x86 一致。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>Linux ubuntu 2.6.24-22-generic #1 SMP Mon Nov 24 19:35:06 UTC 2008 x86_64 GNU/Linux</li>
<li>./a.out</li>
<li>Segmentation fault</li></ol></div>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>NetBSD sdf 2.1.0_STABLE NetBSD 2.1.0_STABLE (sdf) #0: Fri Mar 30 02:24:32 UTC 2007&nbsp; root@ol:/var/sys/arch/alpha/compile/sdf alpha</li>
<li>./a.out</li>
<li>Memory fault (core dumped)</li></ol></div>
<p>　　有趣的是在 XScale（Intel 实现的 ARMv5）下，虽然同属 little-endian，但非对齐取数时出现了在字内按字节循环的移位的结果。查查 <a href="http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0097a/armcc_ch04s05s02.htm">ARM 的官方文档</a>，这确实是 ARMv5 的特性；而在 ARMv6 以后，非对齐访问则是完全支持的。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>Linux zaurus 2.4.18-rmk7-pxa3-embedix #1 Sat, 06 Aug 2005 12:22:55 +0000 armv5tel unknown</li>
<li>./a.out </li>
<li>14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51 </li>
<li>11121314 14111213 13141112 12131411 21222324 24212223 23242122 22232421</li></ol></div>
<p>　　接下来看看 PowerPC，它是 big-endian 的代表，允许 32 位以内的非对齐访问，结果是容易理解的。有关 PowerPC 非对齐访问的一些细节可以参考<a href="http://www.ibm.com/developerworks/library/pa-dalign/">这篇文章</a>。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>AIX aix 3 5 00C97AC04C00 powerpc unknown AIX</li>
<li>./a.out</li>
<li>11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54</li>
<li>11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233</li></ol></div>
<p>　　同样是 big-endium 的 SPARC 则不允许非对齐访问。它会对非对齐访问抛出 SIGBUS。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris</li>
<li>./a.out</li>
<li>11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54</li>
<li>Bus Error (core dumped)</li></ol></div>
<p>　　最后看看我们中科院计算所的龙芯（Loongson）2E，它是兼容 MIPS 架构的处理器。很多教科书告诉我们说通常的 MIPS 是不允许非对齐访问的（部分 MIPS 实现提供了非对齐访问指令，并申请了专利），但我们在龙芯下却得到了和 x86 相同的、允许非对齐访问的结果，这又是为什么呢？初步查到的原因是“<a href="http://www.lemote.com/bbs/archiver/?tid-7667.html">（针对龙芯修改过的 Linux）内核里确实有一个异常处理函数负责处理 lw 访问非对齐地址引起的异常</a>”。这也许是龙芯绕开 MIPS 专利的一种办法？我会向龙芯团队的同学求证一下，也希望熟悉 MIPS 或龙芯的朋友给我一个确切的答案。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>Linux Loongson-1 2.6.18.1lemote #1 Sat Jan 13 16:02:26 CST 2007 mips GNU/Linux</li>
<li>./a.out</li>
<li>14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51</li>
<li>11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421</li></ol></div>
<p>　　不过用心思考的朋友也许会发现上面一系列实验存在的一个疏漏：没有考虑编译器的影响。一方面，编译器可能对整型的字长有不同的规定（例如 Windows 下的某些编译器即使在 32 位 x86 上也会把 int 定义为 16 位）；另一方面，编译器可以对不支持非对齐访问的处理器生成一定的指令序列、通过多次访存来模拟非对齐访问。我们看下面的例子：还是在 SPARC 平台上，改用 Solaris 自带的 Sun CC 来编译实验程序，这时就不会出现“Bus Error”，而会输出和 PowerPC 一样的结果。因为 SunCC 默认会使用“<a href="http://docs.sun.com/app/docs/doc/820-7598/bjavc">-xmemalign</a>”参数来生成适当的访存指令序列。</p>
<div class="hl-surround"><ol class="hl-main ln-show" title="Double click to hide line number." ondblclick = "linenumber(this)"><li class="hl-firstline">uname -a</li>
<li>SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris</li>
<li>cc data.c</li>
<li>./a.out</li>
<li>11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54</li>
<li>11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233</li></ol></div>
<p>　　这样看来，在不指定机器架构和编译器等上下文的情况下，要正确且完美地回答一开始的那道题目还是需要一定知识积累的。答案省略，留给大家自己求解。在面试、笔试诸如 Sun SPARC、IBM PowerPC、中科院计算所微处理器中心等部门或者做 ARM 等嵌入式开发的公司时，最好先了解清楚它们的产品常识。<br />
　　（部分实验环境来源于 <a href="http://www.unix-center.net/">Unix-Center.Net</a>，在此致谢）</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/c-pointer-alignment-access/">http://blog.linjian.org/articles/c-pointer-alignment-access/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/" title="编译器与标准——严格还是智能？">编译器与标准——严格还是智能？</a></li><li><a href="http://blog.linjian.org/articles/c-cpp-register-difference/" title="C和C++处理register关键字的一处差异">C和C++处理register关键字的一处差异</a></li><li><a href="http://blog.linjian.org/articles/sizeof-interesting-problems/" title="sizeof的一些牛角尖问题">sizeof的一些牛角尖问题</a></li><li><a href="http://blog.linjian.org/articles/innovation-exploration-loongson/" title="从《创新求索录》看龙芯的“自主创新”">从《创新求索录》看龙芯的“自主创新”</a></li><li><a href="http://blog.linjian.org/articles/windows-posix-sua/" title="试用Windows的UNIX/POSIX子系统（SUA）">试用Windows的UNIX/POSIX子系统（SUA）</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/c-pointer-alignment-access/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>对“为人民计算”的几点思考</title>
		<link>http://blog.linjian.org/articles/essay-on-c4m/</link>
		<comments>http://blog.linjian.org/articles/essay-on-c4m/#comments</comments>
		<pubDate>Tue, 30 Jun 2009 15:30:03 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[为人民计算]]></category>
		<category><![CDATA[信息]]></category>
		<category><![CDATA[普惠计算]]></category>
		<category><![CDATA[社会]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[计算机]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=355</guid>
		<description><![CDATA[对“为人民计算”的几点思考 （本文为本人“先进计算机和软件技术系列讲座”课程作业） 　 　　“为人民计算”（Computing for the Masses）是近年来计算机科学技术和产业发展的目标之一。而究竟什么是“为人民计算”，出于国情、技术水平和个人认识的差异，不少学者提出过不同的理解。我国人口众多，农村人口占56%（国家统计局2007年10月数据），城乡经济和区域发展尚不协调，正处于信息化带动工业化的发展时期。在这种历史条件下，如何使得信息化的成果真正惠及人民，实现计算机行业所谓“普惠计算”的目标，是业界出于其社会责任感有必要思考的一个问题。针对我的导师徐志伟研究员在“为人民计算”方面的提出的一些观点，我浅谈一下我对几个相关问题的思考。 　 　　消费动机不在信息化本身 　　在受益于信息化且面向大众服务的行业当中，近几年来增长较为显著的是移动通信产业。中国的移动电话用户数量由1999年底的4330万户发展到2009年中期的6.87亿户。城市手机用户已经趋于饱和，但运营商对农村移动通信市场仍有信心。移动通信之所以得到了市场的认可，与其明确的消费目的息息相关。绝大多数消费者购买手机，其基本动机就是为了享受通信的便利；对于相当一些职业的人士，实时的通信可能已经不是“方便”而是“必须”。对于农村新市场的开拓，其难点可能在经济方面，而不在于人民对手机的基本功能和作用的认知。类似的案例还有银行卡。截至2009年6月，全国累计发行银行卡18.88亿张，银行卡的主要用户也分布在城市，农村和欠发达地区受制于经济条件和消费意识，对银行卡产品也许有所误解，但这不能说明银行卡本身的定位有问题，至少它们的功能是相对单一和明确的，满足了人民群众的特定需求。 　　然而，计算机与网络技术对人民大众来说，其直接的消费目的性并不如移动通信、银行卡那样显而易见。在目前阶段，计算机与网络更多地扮演着工具和桥梁的角色。购买计算机、接入互联网，并不能直接解决什么具体问题，计算机和网络解决的问题是通过运行在其上的应用体现出来的。“上网”、“获得信息”、“学习”都是一些相对抽象的动机，不能直接满足低端用户具体一项生活需求。因此在商业化推广中，这些空虚的目标可能不会对低端消费者产生太大的吸引力。即便通过财政支持来发展信息化，这些成果也很可能沦落为地方政绩的象征或闲人娱乐的工具，对生产生活不会有实质的影响。要在2040年建成惠及至少12亿人的信息化社会，我认为不能把民用计算机、网络定位为移动通信那种具有明显消费目的性的产品，而必须改变现有中低端计算机和网络相关产品或服务的社会定位，在应用层面寻找“为人民计算”的突破口。 　　信息化被作为工具的现状令很多从业者，特别是计算机科学研究人员不满：业界不愿意让信息技术变成像楼宇保洁一样必不可少但几乎不能创造价值的服务业，学界不也甘心计算机科学停滞不前，最终被其它学科以工具和技术的名义吞并。上世纪末本世纪初，网格计算的理念曾十分风行，研究人员力图建立一种像电网一样即插即得的信息基础设施，但至今网格技术只在部分专业研究领域发挥了功效，而那种惠及全民的“Great Glocal Grid”还未见雏形。其中主要的原因，我认为是信息资源缺乏像电力资源那样的同质性，抛开技术层面的因素，难以同质化的信息本身就使得潜在的用户无法建立明确的消费动机。但信息的同质化正是一个值得研究的科学问题，这个问题的解决，将使得计算机、网络由独立的工具进化为信息基础设施的地位。我们需要这样的基础设施在社会的后台像电网一样默默无闻地“为人民计算”，而用来明确消费动机、创造商业价值、进而构建信息社会的，不是计算机和网络的实体，而是这一基础设施之上的、像家用电器一样的、但目前尚未被发明的具体应用。 　 　　比“脱贫”更难的是“第二跳” 　　目前我国大多数人口仍处于“信息贫困线”以下，“脱贫”是产业界、学术界和决策层正在着力解决的问题。“脱贫”的方向相对明确。对于产业界，主要需要考虑机器生产和网络运营的成本，让计算机和网络对所有人来说由奢侈品变成日用品；对于学术界，需要摒弃一味追求性能的老路，从功耗、易用性等贴近用户的角度考虑体系结构的设计；决策层则需要提供政策扶持，对相关产业采取优惠措施，对人民群众加以合理引导。但我认为有可能出现困难的，并不在于“脱贫”的“第一跳”，而在于由基本价值向商品价值提升的“第二跳”。 　　还是以银行卡为例，我国银行卡发行数量虽然已经超过人口总量，但真正用卡的人群并不大，相当一部分银行卡是通过任务摊派的方式进入低端市场的，这些卡大都成了没有任何交易记录的“死卡”。即使是使用银行卡的人，他们中的多数也只享受了其商品价值，把它当作一张可以自助取款的存折，从未使用甚至从未听说过银行卡所关联的高级金融服务。 　　纵观我国计算机网络用户，其层次分化较为明显。IT界的精英人士、具有探索精神的专业人士早已享受到了信息产业的泛在价值、专业价值乃至个性价值，他们甚至可以在软件开发商与网络运营商尚未提供相关服务的情况下发扬自主创新的DIY精神，主动地让信息化为自己创造价值。而目前数目最庞大的用户群是将计算机、网络作为工具的人群，他们虽然享受了信息化带来的基本价值和商品价值，但只是在工作和生活中为了特定的目的有意去使用计算机和网络，计算机和网络更多地作为间接、高效实现传统生产模式价值的途径，而不是直接为这些用户提供新的价值。在农村和欠发达地区也有一定数量的计算机、网络用户，网吧是他们接触信息化成果的主要场所。这些用户缺乏自主探索的意识，他们通常使用仅仅使用网吧预置的网站、软件与游戏。即使是这些地区的一些官员，对信息产业的理解也有可能过于狭隘，甚至可能因为未成年人沉迷网游等问题对计算机、网络建立负面的印象，进而抵制信息化进程。然而要达到“普惠计算”的目标，必须把这类人群作为未来市场的潜在用户。目前我国的信息化还没有像工业化那样彻底地改变大多数人的生活方式；在相当一部分地区，工业化的成果甚至还没有全面应用到生产中，在这些地区谈信息化的价值，确实不容易为当地政府和人民大众所接受。单纯的基础设施建设所带来的基本价值就像提供给毫无养殖经验的农户的良种牲畜，他们只能一吃了之，不懂得通过繁殖子代、获取副产品来增值。既然信息化带动工业化已经是中国特色的跨越式发展模式，我们就应该在“脱贫”的同时完成“第二跳”的工作，提供一系列能够直接服务于生产、生活的应用。只有完成“第二跳”，信息化才不再是软硬件堆叠的花架子，其价值才能使个人用户受益。如何让人民群众真正从“为人民计算”中得到实惠，让计算机科学的成果成为人民群众喜闻乐见的事物，而不是像银行卡市场那样徒有虚名，这才是比信息“脱贫”更重要的任务。 　 　　对“计算透镜”半保守半超前的认识 　　Richard M. Karp提出的“计算透镜”（Computational Lens）理念被认为是未来二十年计算机科学可能的发展方向之一。其核心理念是将计算作为一种通用的思维方式，通过这种广义的计算（涉及信息、执行算法、关注复杂度）来描述各类自然过程和社会过程，从而解决各个学科的问题。Karp给出了计算透镜理论在生物科学中的成功应用。这一理论试图将计算机科学由最初的数值计算工具、仿真与可视化技术以及后来基于网络、面向多学科的e-Science平台，变成普遍适用于自然和社会领域的通用思维模式。从这一点上看，计算透镜的思路也似乎是要将计算机从传统的“为军事计算”、“为科学计算”、“为商业计算”过渡到“为人民计算”、“为人类计算”，只是这一思维工具并不一定直接为人民所使用，而是要借助学界和业界之手将计算透镜带来的好处通过生产力的发展来施惠与人。 　　世界真的可以用计算过程来表达吗？在初次接触到计算透镜的概念时，我首先想到的是近代机械唯物主义的自然观，试图用力学和机械的观点来解释并分析自然与社会现象。进而联想到了人工智能发展史上的符号主义、连接主义和行为主义等学派的基本观点，它们都力图用一种简单的形式化模型来解释智慧和思维。计算透镜是不是在走这些老路呢？我个人感觉，在图灵机模型和现行的可计算性理论本身是否符合自然规律尚未被证实的前提下，不要指望计算透镜能在短期内给一般大众的工作和生活带来什么影响。但对计算透镜的研究是有意义的，其意义可能不在于用计算改造自然，而在于更好地认识自然，从而改造现有的计算模型，使之能够像一些自然过程一样高效。也许到那个时候，人、机、物界限因为思维方式的统一而变得模糊，“为人民计算”的理念演化为对人类思维能力的直接扩展，届时作为中介的信息化工具、应用可能反而变得不重要了。 　　回到现实，这方面已经存在的一些问题需要引起我们的关注。计算透镜理论中包含“社会计算”。尽管社会计算的概念目前还没有精确的定义，无论是用计算机的方法研究社会还是用社会学的方法研究计算机，统统被一些机构打上了“社会计算”的标签。从“社会”二字可以看出，这一计算的执行也并非人民的个体可以完成的，要做到社会计算，仍需要掌握着社会数据和社会决策权的机构来完成。但在法治完善程度赶不上技术发展速度的情况下，如何保证社会计算的成果真正服务于民，成为“为人民计算”的理念与实践需要面对的一个重要的非技术问题。例如当前舆情计算技术的发展确实为政府机关提供了迅速准确的决策支持，但一些社会团体对舆情计算中的个人隐私保护问题仍存在争议，认为这对网民存在侵权。舆情计算技术还被某些搜索引擎公司或网络公关公司所利用，通过推送正面信息、屏蔽负面评价的方式从不法商家手中谋利，直接侵害了消费者的知情权，损害了市场的公平竞争。从这一点可以看出，“为人民计算”不是一两个新理论、新技术就可以实现的，它需要完善的法治框架、良好的业界环境，以及官、产、学、研各方对人民利益高度负责的态度。 　 　　总之，我心中的“为人民计算”应该是由有价值的应用所导向的、带来明确消费动机的、架构在信息基础设施之上的信息化发展模式。它应该像电网和电器一样易用，让人民受益的同时却感觉不到信息基础设施的存在；它应该突破城乡与区域差异，惠及全民，为信息化带动工业化带来历史契机；它应该兼顾可用性、保密性与可控性，在具有社会负责感的机构的运营下真正代表人民群众的利益。 　 参考文献 [1] 徐志伟．为人民计算的三个问题．中国计算机学会通讯，2008(10)． [2] 江泽民．新时期我国信息技术产业的发展．上海交通大学学报，2008(10)． [3] 李国杰．创新求索录．电子工业出版社，2008． [4] Richard M. Karp. Computer Science as a Lens on the Sciences. ICDCS 2008. 原文链接：http://blog.linjian.org/articles/essay-on-c4m/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: center; font-size: 16pt; "><strong>对“为人民计算”的几点思考</strong></p>
<p style="text-align: center; ">（本文为本人“先进计算机和软件技术系列讲座”课程作业）</p>
<p>　<br />
　　“为人民计算”（Computing for the Masses）是近年来计算机科学技术和产业发展的目标之一。而究竟什么是“为人民计算”，出于国情、技术水平和个人认识的差异，不少学者提出过不同的理解。我国人口众多，农村人口占56%（国家统计局2007年10月数据），城乡经济和区域发展尚不协调，正处于信息化带动工业化的发展时期。在这种历史条件下，如何使得信息化的成果真正惠及人民，实现计算机行业所谓“普惠计算”的目标，是业界出于其社会责任感有必要思考的一个问题。针对我的导师徐志伟研究员在“为人民计算”方面的提出的一些观点，我浅谈一下我对几个相关问题的思考。<br />
<a href="http://picasaweb.google.com/lh/photo/TEIF5qFbf2nZZeuR-bOc6A?feat=embedwebsite"><img src="http://www.linjian.org/imagoxy/getimg.php?u=nBnauUHevADM4M3LrxWSE90S5BlQRZzLzFTQBFUQBFUQBF0LJh0RvNzXkp3brN1LZFlQFlXMU1yXRp1Xv02bj5CdoB3Zn5iNox2LvoDc0RHa" title="" alt="对“为人民计算”的几点思考" /></a><br />
　<br />
　　<strong>消费动机不在信息化本身</strong><br />
　　在受益于信息化且面向大众服务的行业当中，近几年来增长较为显著的是移动通信产业。中国的移动电话用户数量由1999年底的4330万户发展到2009年中期的6.87亿户。城市手机用户已经趋于饱和，但运营商对农村移动通信市场仍有信心。移动通信之所以得到了市场的认可，与其明确的消费目的息息相关。绝大多数消费者购买手机，其基本动机就是为了享受通信的便利；对于相当一些职业的人士，实时的通信可能已经不是“方便”而是“必须”。对于农村新市场的开拓，其难点可能在经济方面，而不在于人民对手机的基本功能和作用的认知。类似的案例还有银行卡。截至2009年6月，全国累计发行银行卡18.88亿张，银行卡的主要用户也分布在城市，农村和欠发达地区受制于经济条件和消费意识，对银行卡产品也许有所误解，但这不能说明银行卡本身的定位有问题，至少它们的功能是相对单一和明确的，满足了人民群众的特定需求。<br />
　　然而，计算机与网络技术对人民大众来说，其直接的消费目的性并不如移动通信、银行卡那样显而易见。在目前阶段，计算机与网络更多地扮演着工具和桥梁的角色。购买计算机、接入互联网，并不能直接解决什么具体问题，计算机和网络解决的问题是通过运行在其上的应用体现出来的。“上网”、“获得信息”、“学习”都是一些相对抽象的动机，不能直接满足低端用户具体一项生活需求。因此在商业化推广中，这些空虚的目标可能不会对低端消费者产生太大的吸引力。即便通过财政支持来发展信息化，这些成果也很可能沦落为地方政绩的象征或闲人娱乐的工具，对生产生活不会有实质的影响。要在2040年建成惠及至少12亿人的信息化社会，我认为不能把民用计算机、网络定位为移动通信那种具有明显消费目的性的产品，而必须改变现有中低端计算机和网络相关产品或服务的社会定位，在应用层面寻找“为人民计算”的突破口。<br />
　　信息化被作为工具的现状令很多从业者，特别是计算机科学研究人员不满：业界不愿意让信息技术变成像楼宇保洁一样必不可少但几乎不能创造价值的服务业，学界不也甘心计算机科学停滞不前，最终被其它学科以工具和技术的名义吞并。上世纪末本世纪初，网格计算的理念曾十分风行，研究人员力图建立一种像电网一样即插即得的信息基础设施，但至今网格技术只在部分专业研究领域发挥了功效，而那种惠及全民的“Great Glocal Grid”还未见雏形。其中主要的原因，我认为是信息资源缺乏像电力资源那样的同质性，抛开技术层面的因素，难以同质化的信息本身就使得潜在的用户无法建立明确的消费动机。但信息的同质化正是一个值得研究的科学问题，这个问题的解决，将使得计算机、网络由独立的工具进化为信息基础设施的地位。我们需要这样的基础设施在社会的后台像电网一样默默无闻地“为人民计算”，而用来明确消费动机、创造商业价值、进而构建信息社会的，不是计算机和网络的实体，而是这一基础设施之上的、像家用电器一样的、但目前尚未被发明的具体应用。<br />
　<br />
　　<strong>比“脱贫”更难的是“第二跳”</strong><br />
　　目前我国大多数人口仍处于“信息贫困线”以下，“脱贫”是产业界、学术界和决策层正在着力解决的问题。“脱贫”的方向相对明确。对于产业界，主要需要考虑机器生产和网络运营的成本，让计算机和网络对所有人来说由奢侈品变成日用品；对于学术界，需要摒弃一味追求性能的老路，从功耗、易用性等贴近用户的角度考虑体系结构的设计；决策层则需要提供政策扶持，对相关产业采取优惠措施，对人民群众加以合理引导。但我认为有可能出现困难的，并不在于“脱贫”的“第一跳”，而在于由基本价值向商品价值提升的“第二跳”。<br />
　　还是以银行卡为例，我国银行卡发行数量虽然已经超过人口总量，但真正用卡的人群并不大，相当一部分银行卡是通过任务摊派的方式进入低端市场的，这些卡大都成了没有任何交易记录的“死卡”。即使是使用银行卡的人，他们中的多数也只享受了其商品价值，把它当作一张可以自助取款的存折，从未使用甚至从未听说过银行卡所关联的高级金融服务。<br />
　　纵观我国计算机网络用户，其层次分化较为明显。IT界的精英人士、具有探索精神的专业人士早已享受到了信息产业的泛在价值、专业价值乃至个性价值，他们甚至可以在软件开发商与网络运营商尚未提供相关服务的情况下发扬自主创新的DIY精神，主动地让信息化为自己创造价值。而目前数目最庞大的用户群是将计算机、网络作为工具的人群，他们虽然享受了信息化带来的基本价值和商品价值，但只是在工作和生活中为了特定的目的有意去使用计算机和网络，计算机和网络更多地作为间接、高效实现传统生产模式价值的途径，而不是直接为这些用户提供新的价值。在农村和欠发达地区也有一定数量的计算机、网络用户，网吧是他们接触信息化成果的主要场所。这些用户缺乏自主探索的意识，他们通常使用仅仅使用网吧预置的网站、软件与游戏。即使是这些地区的一些官员，对信息产业的理解也有可能过于狭隘，甚至可能因为未成年人沉迷网游等问题对计算机、网络建立负面的印象，进而抵制信息化进程。然而要达到“普惠计算”的目标，必须把这类人群作为未来市场的潜在用户。目前我国的信息化还没有像工业化那样彻底地改变大多数人的生活方式；在相当一部分地区，工业化的成果甚至还没有全面应用到生产中，在这些地区谈信息化的价值，确实不容易为当地政府和人民大众所接受。单纯的基础设施建设所带来的基本价值就像提供给毫无养殖经验的农户的良种牲畜，他们只能一吃了之，不懂得通过繁殖子代、获取副产品来增值。既然信息化带动工业化已经是中国特色的跨越式发展模式，我们就应该在“脱贫”的同时完成“第二跳”的工作，提供一系列能够直接服务于生产、生活的应用。只有完成“第二跳”，信息化才不再是软硬件堆叠的花架子，其价值才能使个人用户受益。如何让人民群众真正从“为人民计算”中得到实惠，让计算机科学的成果成为人民群众喜闻乐见的事物，而不是像银行卡市场那样徒有虚名，这才是比信息“脱贫”更重要的任务。<br />
　<br />
　　<strong>对“计算透镜”半保守半超前的认识</strong><br />
　　Richard M. Karp提出的“计算透镜”（Computational Lens）理念被认为是未来二十年计算机科学可能的发展方向之一。其核心理念是将计算作为一种通用的思维方式，通过这种广义的计算（涉及信息、执行算法、关注复杂度）来描述各类自然过程和社会过程，从而解决各个学科的问题。Karp给出了计算透镜理论在生物科学中的成功应用。这一理论试图将计算机科学由最初的数值计算工具、仿真与可视化技术以及后来基于网络、面向多学科的e-Science平台，变成普遍适用于自然和社会领域的通用思维模式。从这一点上看，计算透镜的思路也似乎是要将计算机从传统的“为军事计算”、“为科学计算”、“为商业计算”过渡到“为人民计算”、“为人类计算”，只是这一思维工具并不一定直接为人民所使用，而是要借助学界和业界之手将计算透镜带来的好处通过生产力的发展来施惠与人。<br />
　　世界真的可以用计算过程来表达吗？在初次接触到计算透镜的概念时，我首先想到的是近代机械唯物主义的自然观，试图用力学和机械的观点来解释并分析自然与社会现象。进而联想到了人工智能发展史上的符号主义、连接主义和行为主义等学派的基本观点，它们都力图用一种简单的形式化模型来解释智慧和思维。计算透镜是不是在走这些老路呢？我个人感觉，在图灵机模型和现行的可计算性理论本身是否符合自然规律尚未被证实的前提下，不要指望计算透镜能在短期内给一般大众的工作和生活带来什么影响。但对计算透镜的研究是有意义的，其意义可能不在于用计算改造自然，而在于更好地认识自然，从而改造现有的计算模型，使之能够像一些自然过程一样高效。也许到那个时候，人、机、物界限因为思维方式的统一而变得模糊，“为人民计算”的理念演化为对人类思维能力的直接扩展，届时作为中介的信息化工具、应用可能反而变得不重要了。<br />
　　回到现实，这方面已经存在的一些问题需要引起我们的关注。计算透镜理论中包含“社会计算”。尽管社会计算的概念目前还没有精确的定义，无论是用计算机的方法研究社会还是用社会学的方法研究计算机，统统被一些机构打上了“社会计算”的标签。从“社会”二字可以看出，这一计算的执行也并非人民的个体可以完成的，要做到社会计算，仍需要掌握着社会数据和社会决策权的机构来完成。但在法治完善程度赶不上技术发展速度的情况下，如何保证社会计算的成果真正服务于民，成为“为人民计算”的理念与实践需要面对的一个重要的非技术问题。例如当前舆情计算技术的发展确实为政府机关提供了迅速准确的决策支持，但一些社会团体对舆情计算中的个人隐私保护问题仍存在争议，认为这对网民存在侵权。舆情计算技术还被某些搜索引擎公司或网络公关公司所利用，通过推送正面信息、屏蔽负面评价的方式从不法商家手中谋利，直接侵害了消费者的知情权，损害了市场的公平竞争。从这一点可以看出，“为人民计算”不是一两个新理论、新技术就可以实现的，它需要完善的法治框架、良好的业界环境，以及官、产、学、研各方对人民利益高度负责的态度。<br />
　<br />
　　总之，我心中的“为人民计算”应该是由有价值的应用所导向的、带来明确消费动机的、架构在信息基础设施之上的信息化发展模式。它应该像电网和电器一样易用，让人民受益的同时却感觉不到信息基础设施的存在；它应该突破城乡与区域差异，惠及全民，为信息化带动工业化带来历史契机；它应该兼顾可用性、保密性与可控性，在具有社会负责感的机构的运营下真正代表人民群众的利益。<br />
　<br />
<strong>参考文献</strong><br />
[1] 徐志伟．为人民计算的三个问题．中国计算机学会通讯，2008(10)．<br />
[2] 江泽民．新时期我国信息技术产业的发展．上海交通大学学报，2008(10)．<br />
[3] 李国杰．创新求索录．电子工业出版社，2008．<br />
[4] Richard M. Karp. Computer Science as a Lens on the Sciences. ICDCS 2008.</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/essay-on-c4m/">http://blog.linjian.org/articles/essay-on-c4m/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/smartq-v5-mifi/" title="使用智器 V5 实现山寨 MiFi">使用智器 V5 实现山寨 MiFi</a></li><li><a href="http://blog.linjian.org/articles/s3-sync-tools/" title="三款面向 Amazon S3 的开源文件同步工具之对比">三款面向 Amazon S3 的开源文件同步工具之对比</a></li><li><a href="http://blog.linjian.org/articles/ipv6-icmpv6-error/" title="解决 IPv6 路由发现协议得到错误地址的问题">解决 IPv6 路由发现协议得到错误地址的问题</a></li><li><a href="http://blog.linjian.org/articles/rsync-unison-ssh/" title="使用 rsync 或 unison 备份或同步支持 ssh 的 web 主机">使用 rsync 或 unison 备份或同步支持 ssh 的 web 主机</a></li><li><a href="http://blog.linjian.org/articles/smart-input-method/" title="智能输入法软件的社会责任问题">智能输入法软件的社会责任问题</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/essay-on-c4m/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>《程序员的自我修养》是有学习价值的</title>
		<link>http://blog.linjian.org/articles/bv-link-load-lib/</link>
		<comments>http://blog.linjian.org/articles/bv-link-load-lib/#comments</comments>
		<pubDate>Sat, 23 May 2009 06:20:43 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[书评]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[编译]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=343</guid>
		<description><![CDATA[　　不久前收到了博文视点寄来的新书——《程序员的自我修养——链接、装载与库》。比较巧的是，我这段时间正在从事一个涉及多语言互操作的项目，那些天还在看一些有关C++语言设计和实现原理的文献，这本《程序员的自我修养》和我最近的工作还是多多少少有点联系的。 　　有的人可能认为读博士的人不应该再去关注这些面向程序员、纠结于技术细节的书了，而要专心于自己的研究领域，做偏科学、偏理论的事。这是有道理的，博士教育和硕士教育的差异即在此，漂泊在茫无涯际的技术海洋很可能让本应该专注于一个方向的人不知所向。但是我们也要反思，自己的基础是否足够扎实？对计算机原理的理解是否超越了那些具体技术的层面？自己是否真的具备了从事科研的素质？我的导师在他的科普作品《电脑启示录》（中篇 硅谷的秘密）中曾提到作为计算机科研人员应该具备的基本素质，并例举了几个用来考察这些素质的问题，诸如：从计算机开机到操作系统等待用户输入，经历的一系列流程是什么？在浏览器里敲入一个网址到网页呈现给用户，期间又有哪些工作细节？这些过程看似稀疏平常，其中的大道理在本科计算机课程中也都或多或少地介绍过，然而要精确地表达每个步骤，更重要地是说明每个步骤为什么要这样设计、为什么会这样实现、其中的科学依据是什么，往往不是每个计算机专业毕业生都能说清楚的。这类问题常常能从侧面反映一个从业人员的理论功底及实践经验。《程序员的自我修养》所阐述的也正是同一类的问题：一个程序由硬盘上目标文件、可执行文件变成内存中的进程体、CPU中的指令流，整个过程的来龙去脉是什么，有什么原理、诀窍、讲究和因果联系。 　　在计算机领域从事不同具体工作的科研、技术人员，静下心来分析一下这些平时被各种层面的接口掩盖了的机制是有好处的。初用VC++编程的时候，也许你会奇怪，监视窗口为什么要输出一串“烫”字；干Linux工程时，仅仅链接了几个标准库文件就把程序搞崩溃了，你会认为这是链接器或标准库的bug，还是自己没有弄明白它们的机理？如果你在计算机学科的其它方面有过一些细致的了解，你又会发现，像自举引导、延迟绑定等共性的方法，时间与空间转换、策略与机制分离等共性的原则在编译、链接、装载过程中也都有生动的体现。而即使你是做研究的，方向与程序原理相隔甚远，看看这些计算机领域内实现相对成熟、应用相对普遍的、原理相对通用的问题，对自己的工作也很有启发意义。 　　尽管这本书有待市场的考验，但它研究的问题确属计算机学科中的经典。把理解链接、装载与库作为程序员的自我修养是否合适？我想，它算不上充分条件，不过确实是一个必要条件。不仅对程序员来说必要，对任何一个从事计算机研究与开发的人来说都是有价值的。 原文链接：http://blog.linjian.org/articles/bv-link-load-lib/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章复习C++，看了点书编译器与标准——严格还是智能？我期望的《编程之美》使用C++实现的Java语言子集词法、语法、语义分析器探访唐家岭，反思“蚁族”]]></description>
			<content:encoded><![CDATA[<p>　　不久前收到了<a href="http://www.broadview.com.cn/">博文视点</a>寄来的新书——<a href="http://www.china-pub.com/195439">《程序员的自我修养——链接、装载与库》</a>。比较巧的是，我这段时间正在从事一个涉及多语言互操作的项目，那些天还在看一些有关C++语言设计和实现原理的文献，这本《程序员的自我修养》和我最近的工作还是多多少少有点联系的。<br />
　　有的人可能认为读博士的人不应该再去关注这些面向程序员、纠结于技术细节的书了，而要专心于自己的研究领域，做偏科学、偏理论的事。这是有道理的，博士教育和硕士教育的差异即在此，漂泊在茫无涯际的技术海洋很可能让本应该专注于一个方向的人不知所向。但是我们也要反思，自己的基础是否足够扎实？对计算机原理的理解是否超越了那些具体技术的层面？自己是否真的具备了从事科研的素质？<a href="http://www.ict.ac.cn/survey/channel/detail814.asp">我的导师</a>在他的科普作品<a href="http://www.china-pub.com/6839">《电脑启示录》（中篇 硅谷的秘密）</a>中曾提到作为计算机科研人员应该具备的基本素质，并例举了几个用来考察这些素质的问题，诸如：从计算机开机到操作系统等待用户输入，经历的一系列流程是什么？在浏览器里敲入一个网址到网页呈现给用户，期间又有哪些工作细节？这些过程看似稀疏平常，其中的大道理在本科计算机课程中也都或多或少地介绍过，然而要精确地表达每个步骤，更重要地是说明每个步骤为什么要这样设计、为什么会这样实现、其中的科学依据是什么，往往不是每个计算机专业毕业生都能说清楚的。这类问题常常能从侧面反映一个从业人员的理论功底及实践经验。《程序员的自我修养》所阐述的也正是同一类的问题：一个程序由硬盘上目标文件、可执行文件变成内存中的进程体、CPU中的指令流，整个过程的来龙去脉是什么，有什么原理、诀窍、讲究和因果联系。<br />
　　在计算机领域从事不同具体工作的科研、技术人员，静下心来分析一下这些平时被各种层面的接口掩盖了的机制是有好处的。初用VC++编程的时候，也许你会奇怪，监视窗口为什么要输出一串“烫”字；干Linux工程时，仅仅链接了几个标准库文件就把程序搞崩溃了，你会认为这是链接器或标准库的bug，还是自己没有弄明白它们的机理？如果你在计算机学科的其它方面有过一些细致的了解，你又会发现，像自举引导、延迟绑定等共性的方法，时间与空间转换、策略与机制分离等共性的原则在编译、链接、装载过程中也都有生动的体现。而即使你是做研究的，方向与程序原理相隔甚远，看看这些计算机领域内实现相对成熟、应用相对普遍的、原理相对通用的问题，对自己的工作也很有启发意义。<br />
　　尽管这本书有待市场的考验，但它研究的问题确属计算机学科中的经典。把理解链接、装载与库作为程序员的自我修养是否合适？我想，它算不上充分条件，不过确实是一个必要条件。不仅对程序员来说必要，对任何一个从事计算机研究与开发的人来说都是有价值的。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/bv-link-load-lib/">http://blog.linjian.org/articles/bv-link-load-lib/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/cpp-review-books/" title="复习C++，看了点书">复习C++，看了点书</a></li><li><a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/" title="编译器与标准——严格还是智能？">编译器与标准——严格还是智能？</a></li><li><a href="http://blog.linjian.org/articles/the-beauty-of-programming-expected/" title="我期望的《编程之美》">我期望的《编程之美》</a></li><li><a href="http://blog.linjian.org/articles/tiny-java-compiler/" title="使用C++实现的Java语言子集词法、语法、语义分析器">使用C++实现的Java语言子集词法、语法、语义分析器</a></li><li><a href="http://blog.linjian.org/articles/tangjialing-ants/" title="探访唐家岭，反思“蚁族”">探访唐家岭，反思“蚁族”</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/bv-link-load-lib/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>C和C++处理register关键字的一处差异</title>
		<link>http://blog.linjian.org/articles/c-cpp-register-difference/</link>
		<comments>http://blog.linjian.org/articles/c-cpp-register-difference/#comments</comments>
		<pubDate>Sun, 19 Apr 2009 15:08:02 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[register]]></category>
		<category><![CDATA[标准]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=330</guid>
		<description><![CDATA[　　C++并不是完全兼容C语言的，上次提到的sizeof('a')等于几的问题就是一例。今天我在编码时又无意中发现了一处不同： 　　用register关键字修饰的变量，在C语言中是不可以用&#038;操作符取地址的，这是我已有的经验。因为编译器如果接受了程序员的建议把变量存入寄存器，它是不存在虚拟地址的。但在C++中，用register修饰的变量可以用&#038;操作符取地址，这是我在一段代码中发现的。如果程序中显式取了register变量的地址，编译器一定会将这个变量定义在内存中，而不会定义为寄存器变量。 　　我在C99（ISO/IEC 9899:1999）和ISO C++（ISO/IEC 14882:2003）标准中得到了确认，C和C++标准对register遇到&#038;的处理确实有不同的明确定义。但为什么要这样定义？我只能从标准的字里行间猜测。K&#038;R C1中如何描述register我尚未查证，K&#038;R C2（ANSI C）中说明了“register variables are to be placed in machine registers ... but compilers are free to ignore the advice ”。但在C99和ISO C++中，措辞分别变成：“suggests that access to the object be as fast as possible”、“a hint to the implementation that the object so declared will be heavily used”，不再特别提及“machine registers”。可见历史上register关键字在强调尽可能地把变量保存到寄存器，而现在的register关键字不再强调具体手段，只是建议编译器通过各种可行的方式优化该变量的访问（不过很多编译器会忽略这一关键字，而采用自身的优化策略）。C99可能是为了保持对K&#038;R C的兼容而不允许取地址操作；而C++也许是因为没有历史包袱才放宽了这个限制吧。猜测而已，希望知道内幕的朋友告诉我更精确的答案。 原文链接：http://blog.linjian.org/articles/c-cpp-register-difference/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 [...]]]></description>
			<content:encoded><![CDATA[<p>　　C++并不是完全兼容C语言的，上次提到的<a href="http://blog.linjian.org/articles/sizeof-interesting-problems/">sizeof('a')等于几</a>的问题就是一例。今天我在编码时又无意中发现了一处不同：<br />
　　用register关键字修饰的变量，在C语言中是不可以用&#038;操作符取地址的，这是我已有的经验。因为编译器如果接受了程序员的建议把变量存入寄存器，它是不存在虚拟地址的。但在C++中，用register修饰的变量可以用&#038;操作符取地址，这是我在一段代码中发现的。如果程序中显式取了register变量的地址，编译器一定会将这个变量定义在内存中，而不会定义为寄存器变量。<br />
　　我在C99（ISO/IEC 9899:1999）和ISO C++（ISO/IEC 14882:2003）标准中得到了确认，C和C++标准对register遇到&#038;的处理确实有不同的明确定义。但为什么要这样定义？我只能从标准的字里行间猜测。K&#038;R C1中如何描述register我尚未查证，K&#038;R C2（ANSI C）中说明了“register variables are to be placed in machine registers ... but compilers are free to ignore the advice ”。但在C99和ISO C++中，措辞分别变成：“suggests that access to the object be as fast as possible”、“a hint to the implementation that the object so declared will be heavily used”，不再特别提及“machine registers”。可见历史上register关键字在强调尽可能地把变量保存到寄存器，而现在的register关键字不再强调具体手段，只是建议编译器通过各种可行的方式优化该变量的访问（不过很多编译器会忽略这一关键字，而采用自身的优化策略）。C99可能是为了保持对K&#038;R C的兼容而不允许取地址操作；而C++也许是因为没有历史包袱才放宽了这个限制吧。猜测而已，希望知道内幕的朋友告诉我更精确的答案。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/c-cpp-register-difference/">http://blog.linjian.org/articles/c-cpp-register-difference/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/" title="编译器与标准——严格还是智能？">编译器与标准——严格还是智能？</a></li><li><a href="http://blog.linjian.org/articles/c-pointer-alignment-access/" title="一道 C 语言指针访存题目的引申">一道 C 语言指针访存题目的引申</a></li><li><a href="http://blog.linjian.org/articles/sizeof-interesting-problems/" title="sizeof的一些牛角尖问题">sizeof的一些牛角尖问题</a></li><li><a href="http://blog.linjian.org/articles/lvba-luba/" title="lüba∕luba∕lvba">lüba∕luba∕lvba</a></li><li><a href="http://blog.linjian.org/articles/bv-link-load-lib/" title="《程序员的自我修养》是有学习价值的">《程序员的自我修养》是有学习价值的</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/c-cpp-register-difference/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>对几项带有随机算法思想的工作的讨论</title>
		<link>http://blog.linjian.org/articles/randomized-algorithms-discuss/</link>
		<comments>http://blog.linjian.org/articles/randomized-algorithms-discuss/#comments</comments>
		<pubDate>Sat, 28 Mar 2009 03:38:27 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[随机算法]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=257</guid>
		<description><![CDATA[对几项带有随机算法思想的工作的讨论 （节选自上学期本人组内讨论课作业） 　　在某些类型的数值计算问题中，我们常使用随机算法，如数值概率算法（得到近似解）、拉斯维加斯算法（不一定有解，如果有解一定正确）、蒙特卡罗算法（一定有解，但解不一定正确）、舍伍德算法（一定有解，解一定正确）。这些随机算法的原理基于概率模型，用简单的随机因子代替精确但复杂度高的最优化求解。对于数值概率算法，通过增加计算次数逼近精确解；对于后三类算法，通过增加计算次数提高得到正确解的概率，总体上都可以得到比确定性算法更低的平均时间复杂度。数值概率算法牺牲了解的精确性（Accuracy），但在很多场合下精确性不是首要要求；后三类算法有失去正确性（Correctness）的风险，但由于有办法将失败化为小概率事件，故在实际应用中利远大于弊。 　　在计算机系统，特别是分布式系统的各类问题中，活性（Liveness）与安全性（Safety）往往是评价某种解决方案的关键因素，这类似于数值计算问题中的正确性，通常是不能含糊的，有必要做形式化证明。但这在实际中并不是绝对的。在本学期的讨论课上我们了解了不少工作，其中引入了概率模型，应用类似于随机算法的思想，牺牲某些情况下系统内部绝对的活性与安全性来换取性能的大幅提升，同时引入相关的机制将潜在的失败可控化，不影响系统对外的活性与安全性。这些工作包括： 　　Exterminator：一种能动态修复C、C++程序堆内存错误的运行时库。基于统计规律，在多次运行时重新安排内存布局来定位错误，对错误位置分配额外的内存来降低再次出错的概率，使得原本有内存缺陷的程序可对外表现正常，同时便于开发人员快速定位错误。 　　MetaTM/TxLinux：一类基于事务的内存管理模型。用允许并发访问临界区的事务管理算法代替严格的锁保护机制。同时检测临界区冲突，在发生冲突时回滚重试。它减少了系统同步操作中由严格的锁带来的性能瓶颈。 　　Zyzzyva：一种拜占庭容错协议。一般情况下乐观地采纳主服务器对请求处理顺序的判断，省去了三阶段提交（3PC）协议。同时在客户端检测服务器副本可能的不一致，在发现问题时通知其更正。这种方案减少了3PC协议带来的开销。 ● 我得到的一些启发 　　这些工作中的随机算法思想 　　Exterminator的做法带有数值概率算法的思想。设想一类能够精确地对运行时程序内存进行分析、预测和修复的机制，它应该覆盖所有可能的执行路径，并对有关变量的值域、边界条件等做细致分析，其复杂度有可能是指数级的。但Exterminator仅分析了特定的，也是实际工作中典型的执行路径，通过多次运行随机安排内存布局的实例来找出可以正常运行的实例，并利用内存布局的信息计算每一块内存分配可能引发错误的概率，复杂度下降到了多项式级。在一些基本假设下，作者给出了Exterminator对内存操作问题位置判定正确性概率的公式。Exterminator这样的设计满足了作者所谓 “automatically correcting memory errors with high probability”的需求。作为一种运行时的、治标不治本的纠错机制，使用随机算法很好地平衡了其功能和性能。它所找到的问题位置对机器来说精确度并不高，修补方案也很粗糙，但其分析结果在程序员来看足以发现错误原因并在代码中修正了，具备可用性（尽管目前版本输出信息的可读性不高，但容易改进）。 　　MetaTM/TxLinux和Zyzzyva对原有机制的简化没有破坏它们的活性，但弱化了原有机制的安全性，从这个角度看，它们类似于蒙特卡罗算法。不同的是，数值计算可以通过多次运行来提高解的置信度，而系统算法是有副作用的，不能通过多次运行的方法提高其安全性。MetaTM/TxLinux 和Zyzzyva均采用额外的监督机制来发现错误，在情况不合预期时及时告知算法进程撤销错误操作，重新运行算法或者转而使用原有的安全机制运行。这样，作为一个整体，系统的安全性重新得到了保证。MetaTM/TxLinux的作者没有给出他们这种乐观的并发执行性能的理论证明，但通过在修改之后的 Linux内核上运行多个实际负载，用实验数据说明了事务内存的开销小于传统的锁机制。Zyzzyva的作者同样以实验数据说明为主。可以预测，其假设失败的概率与分布式系统中节点本身失效的概率相关。 　　完备性（Completeness）与可用性（Availability）的权衡 　　上面已经提到，Exterminator的算法肯定不能准确覆盖和修正所有内存错误，但作为一个辅助工具，对用户来说可用性是足够的。对它来说可用性的约束弱于完备性。在MetaTM/TxLinux中，监督机制的加入从理论上保证了系统的安全性，但测试表明对一类频繁更新文件系统同一结点的负载，事务内存的乐观估计失效，临界区冲突激增，系统可用性下降。在这种情况下可以认为可用性的约束强于理论上的完备性。可见，在有些时候系统的完备性与可用性的约束强度不能一概而论，需要针对具体上下文来分析。 　　面向方面程序设计（Aspect Oriented Programming）的思想 　　MetaTM/TxLinux对临界区的控制相比严格的锁要简单不少，但之所以能够对应用保证安全性，是因为它改变了对环境的假设，拓宽了系统边界，加入了外部的监督机制。Zyzzyva中客户端对服务器的监督和反馈也类似。这种通过适当修改外界条件假设，加入外部机制以解决在原有系统闭包内不能或难以解决的问题的方法是值得我们学习和应用的。 　　更进一步地看，将监督反馈机制、垃圾回收机制，乃至活性、安全性这样的抽象性质独立出来，作为一种系统“切面”机制，体现了一定的面向方面的程序设计思想。这类方法或许在GOS将来的设计中有积极意义。 ● 我认为这些工作的不足 　　应用于数值计算的随机算法的平均复杂度和性能都是可以借助概率论证明的，但应用于系统问题的随机算法只能对理想负载计算其性能，在实际利用中负载的特性对系统的性能可能有显著的影响。MetaTM/TxLinux的事务内存的在某些负载下的临界区冲突激增的问题就是一例。作者叙述了这一现象并推测了原因，但没有给出这类问题共性的、内在的原因。如果能够进一步分析这类问题的本质，较为明确地找出不同内存管理模型的适用条件，则可能更具学术和实用意义。如果在此基础上引入一些启发式算法，根据负载的特性调整执行策略，有可能达到更好的效果。 　　MetaTM/TxLinux文章以及会议演示文稿均用相当的篇幅阐述其实验方法和评测结果，作者选择的几个测试用例基本覆盖了Linux内核几大模块的主要原语，但这只是在静态层面上的。内存管理可能遇到的诸多问题只有在不同类型的原语以不同序列组合的动态运行过程中才能充分暴露。因此，如何提高测试用例的说服力是一个问题。 ● 总结 　　我从上述工作中学习到的算法以及其中蕴含的随机算法等思想可以说是一类技术，在初始了解到其巧妙之处后，我曾设想在其它一些经典的系统问题中如何应用这一工具，例如在共识问题（Consensus）中如何快速取得一致。但我觉得有一条原则需要注意，不能为了使用工具而使用工具，要以应用的需求为出发点，合理地分析应用对系统性质的需求，选择最适合的技术方案。这几项带有随机算法思想的工作之所以比较成功，我认为就是因为作者对问题需求把握得好，适时地放弃一些次要目标，另辟蹊径来换取首要问题的高效解决。 原文链接：http://blog.linjian.org/articles/randomized-algorithms-discuss/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章祝贺北理工／六子棋遐想基于BP神经网络的鼠标手势识别实验程序随机数不随机——一个有趣的错误浅议Fibonacci（斐波纳契）数列求解常用迭代方法在求解平方根方面的比较与分析]]></description>
			<content:encoded><![CDATA[<p style="TEXT-ALIGN: center"><strong>对几项带有随机算法思想的工作的讨论</strong><br />
（节选自上学期本人组内讨论课作业）</p>
<p>　　在某些类型的数值计算问题中，我们常使用随机算法，如数值概率算法（得到近似解）、拉斯维加斯算法（不一定有解，如果有解一定正确）、蒙特卡罗算法（一定有解，但解不一定正确）、舍伍德算法（一定有解，解一定正确）。这些随机算法的原理基于概率模型，用简单的随机因子代替精确但复杂度高的最优化求解。对于数值概率算法，通过增加计算次数逼近精确解；对于后三类算法，通过增加计算次数提高得到正确解的概率，总体上都可以得到比确定性算法更低的平均时间复杂度。数值概率算法牺牲了解的精确性（Accuracy），但在很多场合下精确性不是首要要求；后三类算法有失去正确性（Correctness）的风险，但由于有办法将失败化为小概率事件，故在实际应用中利远大于弊。<br />
　　在计算机系统，特别是分布式系统的各类问题中，活性（Liveness）与安全性（Safety）往往是评价某种解决方案的关键因素，这类似于数值计算问题中的正确性，通常是不能含糊的，有必要做形式化证明。但这在实际中并不是绝对的。在本学期的讨论课上我们了解了不少工作，其中引入了概率模型，应用类似于随机算法的思想，牺牲某些情况下系统内部绝对的活性与安全性来换取性能的大幅提升，同时引入相关的机制将潜在的失败可控化，不影响系统对外的活性与安全性。这些工作包括：<br />
　　Exterminator：一种能动态修复C、C++程序堆内存错误的运行时库。基于统计规律，在多次运行时重新安排内存布局来定位错误，对错误位置分配额外的内存来降低再次出错的概率，使得原本有内存缺陷的程序可对外表现正常，同时便于开发人员快速定位错误。<br />
　　MetaTM/TxLinux：一类基于事务的内存管理模型。用允许并发访问临界区的事务管理算法代替严格的锁保护机制。同时检测临界区冲突，在发生冲突时回滚重试。它减少了系统同步操作中由严格的锁带来的性能瓶颈。<br />
　　Zyzzyva：一种拜占庭容错协议。一般情况下乐观地采纳主服务器对请求处理顺序的判断，省去了三阶段提交（3PC）协议。同时在客户端检测服务器副本可能的不一致，在发现问题时通知其更正。这种方案减少了3PC协议带来的开销。</p>
<p><strong>● 我得到的一些启发</strong><br />
　　<strong>这些工作中的随机算法思想</strong><br />
　　Exterminator的做法带有数值概率算法的思想。设想一类能够精确地对运行时程序内存进行分析、预测和修复的机制，它应该覆盖所有可能的执行路径，并对有关变量的值域、边界条件等做细致分析，其复杂度有可能是指数级的。但Exterminator仅分析了特定的，也是实际工作中典型的执行路径，通过多次运行随机安排内存布局的实例来找出可以正常运行的实例，并利用内存布局的信息计算每一块内存分配可能引发错误的概率，复杂度下降到了多项式级。在一些基本假设下，作者给出了Exterminator对内存操作问题位置判定正确性概率的公式。Exterminator这样的设计满足了作者所谓 “automatically correcting memory errors with high probability”的需求。作为一种运行时的、治标不治本的纠错机制，使用随机算法很好地平衡了其功能和性能。它所找到的问题位置对机器来说精确度并不高，修补方案也很粗糙，但其分析结果在程序员来看足以发现错误原因并在代码中修正了，具备可用性（尽管目前版本输出信息的可读性不高，但容易改进）。<br />
　　MetaTM/TxLinux和Zyzzyva对原有机制的简化没有破坏它们的活性，但弱化了原有机制的安全性，从这个角度看，它们类似于蒙特卡罗算法。不同的是，数值计算可以通过多次运行来提高解的置信度，而系统算法是有副作用的，不能通过多次运行的方法提高其安全性。MetaTM/TxLinux 和Zyzzyva均采用额外的监督机制来发现错误，在情况不合预期时及时告知算法进程撤销错误操作，重新运行算法或者转而使用原有的安全机制运行。这样，作为一个整体，系统的安全性重新得到了保证。MetaTM/TxLinux的作者没有给出他们这种乐观的并发执行性能的理论证明，但通过在修改之后的 Linux内核上运行多个实际负载，用实验数据说明了事务内存的开销小于传统的锁机制。Zyzzyva的作者同样以实验数据说明为主。可以预测，其假设失败的概率与分布式系统中节点本身失效的概率相关。<br />
　　<strong>完备性（Completeness）与可用性（Availability）的权衡</strong><br />
　　上面已经提到，Exterminator的算法肯定不能准确覆盖和修正所有内存错误，但作为一个辅助工具，对用户来说可用性是足够的。对它来说可用性的约束弱于完备性。在MetaTM/TxLinux中，监督机制的加入从理论上保证了系统的安全性，但测试表明对一类频繁更新文件系统同一结点的负载，事务内存的乐观估计失效，临界区冲突激增，系统可用性下降。在这种情况下可以认为可用性的约束强于理论上的完备性。可见，在有些时候系统的完备性与可用性的约束强度不能一概而论，需要针对具体上下文来分析。<br />
　　<strong>面向方面程序设计（Aspect Oriented Programming）的思想</strong><br />
　　MetaTM/TxLinux对临界区的控制相比严格的锁要简单不少，但之所以能够对应用保证安全性，是因为它改变了对环境的假设，拓宽了系统边界，加入了外部的监督机制。Zyzzyva中客户端对服务器的监督和反馈也类似。这种通过适当修改外界条件假设，加入外部机制以解决在原有系统闭包内不能或难以解决的问题的方法是值得我们学习和应用的。<br />
　　更进一步地看，将监督反馈机制、垃圾回收机制，乃至活性、安全性这样的抽象性质独立出来，作为一种系统“切面”机制，体现了一定的面向方面的程序设计思想。这类方法或许在GOS将来的设计中有积极意义。</p>
<p><strong>● 我认为这些工作的不足</strong><br />
　　应用于数值计算的随机算法的平均复杂度和性能都是可以借助概率论证明的，但应用于系统问题的随机算法只能对理想负载计算其性能，在实际利用中负载的特性对系统的性能可能有显著的影响。MetaTM/TxLinux的事务内存的在某些负载下的临界区冲突激增的问题就是一例。作者叙述了这一现象并推测了原因，但没有给出这类问题共性的、内在的原因。如果能够进一步分析这类问题的本质，较为明确地找出不同内存管理模型的适用条件，则可能更具学术和实用意义。如果在此基础上引入一些启发式算法，根据负载的特性调整执行策略，有可能达到更好的效果。<br />
　　MetaTM/TxLinux文章以及会议演示文稿均用相当的篇幅阐述其实验方法和评测结果，作者选择的几个测试用例基本覆盖了Linux内核几大模块的主要原语，但这只是在静态层面上的。内存管理可能遇到的诸多问题只有在不同类型的原语以不同序列组合的动态运行过程中才能充分暴露。因此，如何提高测试用例的说服力是一个问题。</p>
<p><strong>● 总结</strong><br />
　　我从上述工作中学习到的算法以及其中蕴含的随机算法等思想可以说是一类技术，在初始了解到其巧妙之处后，我曾设想在其它一些经典的系统问题中如何应用这一工具，例如在共识问题（Consensus）中如何快速取得一致。但我觉得有一条原则需要注意，不能为了使用工具而使用工具，要以应用的需求为出发点，合理地分析应用对系统性质的需求，选择最适合的技术方案。这几项带有随机算法思想的工作之所以比较成功，我认为就是因为作者对问题需求把握得好，适时地放弃一些次要目标，另辟蹊径来换取首要问题的高效解决。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/randomized-algorithms-discuss/">http://blog.linjian.org/articles/randomized-algorithms-discuss/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/connect6-bit-champion/" title="祝贺北理工／六子棋遐想">祝贺北理工／六子棋遐想</a></li><li><a href="http://blog.linjian.org/articles/bp-mouse-gesture-recognition/" title="基于BP神经网络的鼠标手势识别实验程序">基于BP神经网络的鼠标手势识别实验程序</a></li><li><a href="http://blog.linjian.org/articles/random-number-bug/" title="随机数不随机——一个有趣的错误">随机数不随机——一个有趣的错误</a></li><li><a href="http://blog.linjian.org/articles/fibonacci-essay/" title="浅议Fibonacci（斐波纳契）数列求解">浅议Fibonacci（斐波纳契）数列求解</a></li><li><a href="http://blog.linjian.org/articles/solving-square-root-analysis/" title="常用迭代方法在求解平方根方面的比较与分析">常用迭代方法在求解平方根方面的比较与分析</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/randomized-algorithms-discuss/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编译器与标准——严格还是智能？</title>
		<link>http://blog.linjian.org/articles/compiler-standard-strict-smart/</link>
		<comments>http://blog.linjian.org/articles/compiler-standard-strict-smart/#comments</comments>
		<pubDate>Sat, 21 Mar 2009 06:21:05 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[gcc]]></category>
		<category><![CDATA[标准]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=237</guid>
		<description><![CDATA[　　最近做的C/C++工程需要在多种平台下编译（下面所说的编译泛指编译、连接等过程）并运行通过，多种不同版本的编译器考验着代码正确性和严格性。经测试，我们的历史代码中存在着这样几类问题，使得它们在VS2008和低版本gcc下编译通过，但在高版本的gcc下编译就会或多或少地报错： 　　1、使用了外部的函数，但没有在本文件中声明或#include对应的头文件。VS2008和gcc3.2在很多情况下可以智能地从一同编译的其它文件或标准库中找到对应的函数并连接，而gcc4.3要求必须声明。 　　2、使用模板时，T::name表示T::name类型还是T类的name成员？即使不使用typename关键字，VS2008和gcc3.2在很多情况下可以智能推断T::name a;中的T::name表示表示一个类型。但在gcc4.1/4.3下必须使用typename T::name来表示T::name类型。 　　3、隐式类型转换。对于函数Func(string&#038;)，在VS2008中可以采用Func(string("new"));来调用，但gcc不能隐式地转换构造函数的返回，需要使用string str("new"); Func(str);来调用。 　　4、一些copy-paste粗心小问题，比如声明类的成员函数时不应该加“类名::”限定。如果加了，VS2008会忽略，而gcc4.1/4.3会报错。 　　5、非标准的库函数，比如VS2008的itoa（_itoa）。如果说上述几条是“严格性”的问题，这个应该算是“正确性”的问题了———个编译器扩展的内容在另一个编译器看来自然是不知所云。当然，为方便起见，使用编译器提供的非标准扩展也是可以的（比如在不完全支持C99的gcc和VC版本下，分别使用atoq和_atoi64来代替C99的atoll），但这时候别忘了使用条件编译，针对不同的编译器提供不同的代码。 　　等等。 　　总体来看，我们用到的几个编译器的严格程度如下。注意仅仅是从一些小的侧面反映的结果，不完全准确，说明大致情况而已。 　　───────　更严格　──────→ 　　 VS2008　　gcc3.2　　gcc4.1　　gcc4.3 　　←──────　更智能　─────── 　　编译器的严格性也许是一把双刃剑。但我个人认为严格一些还是利大于弊的。拿上面的问题1来说，我就遇到过gcc3.2“智能”连接错误的问题，而如果使用gcc4.3，这个bug就可以轻易地被定位，节约大量的调试时间。 　　智能的底线是正确性。如果代码中出现了不规范的写法，编译器给出智能判断并忽略问题的同时，应该给程序员一定的警告信息，让他们核实代码并判断是否需要向标准的方向修改。不过可悲的是编译器的警告往往被程序员们一略而过了。也许正是因为这一点，gcc才变得越发严格，试图和程序员划清责任域，减少扯皮现象？ 　　当然，仅仅写出语法上严格的代码，解决了不同编译器下编译通过的问题，我们还不能保证程序运行时语义的正确性。例如程序中存在未初始化的变量，一种编译器的实现是以0填充，恰好与程序员的原意相符；而切换到另一编译器下，未初始化的变量成了随机值，程序运行就会出问题。所以，对C/C++这样高度自由的语言来说，严格的编译器也不是万能的，人的因素始终是第一位的。 　　说了这么些严格和智能，评判它们的依据是什么？自然是语言的标准了（C99、ISO C++之类）。有关编译器对语言（包含库）标准的支持，我们大约可以按以下维度划分： 　　　　　　标准要求实现的 　　　　　　　　　↑ 　　　　　　　　　│ 　　标准未定义的　│　标准定义的 　　　　　←───┼───→ 　　　　　　　　　│ 　　　　　　　　　│ 　　　　　　　　　↓ 　　　　　　编译器扩展的 　　标准定义的语法在任何编译器下都应该有相同、无歧义的语义。尽管其内部实现可能不同，或是依赖于具体的机器架构，但在标准中总有明确的说明。而标准中也存在大量未定义的语义，这通常是为了让编译器作者放开限制，做更高效、更简单的实现。这类语法在不同的编译器或不同的运行环境下可能产生不同的结果，经典的例子就是n=m+++m++这种副作用语句的叠加，再如L'abcd'表示什么的问题。 　　工程中通常应该严格禁用标准未定义的语法，因为机器架构、编译器种类和版本、优化选项、运行时环境等的变化都有可能对程序的运行结果产生不可预知的影响。但有些时候标准未定义的的语法也是有用的，比如为了学习理解编译器的原理和优化算法。另外在一些技巧性游戏，如Quine，以及IOCCC、Time Limit Exceeded这样的hack竞赛中，使用未定义语法的捷径也是未尝不可的。 　　而编译器扩展的语法和库则是一类相对有用的工具。不同于标准未定义的内容，编译器所做的扩展也是有严格定义的，不会产生不可预知的结果。在对代码的迁移性要求不高的场合直接使用并无大碍。如果对代码的迁移性有要求，那就如上文所述使用条件编译来限制其作用范围。毕竟编译器所做的扩展是为了弥补标准库的不足，方便开发者。如果对代码将来的使用范围有明显的预知，就需要权衡是否使用编译器的扩展特性。不过就连Linux内核的代码都在使用gcc的扩展语法。 　　在C/C++语言教学中，强调标准是十分必要的。然而市面上的C/C++教材鱼目混珠：那天在师兄的桌上看到的一本国内某知名高校出版的C++教材上，Hello World程序竟有3处明显的问题，不用试验就知道在VS2008或gcc下编译有两处是Error，一处至少是Warning（据说在VC6下可以编译通过）。一些高校计算机教学中普遍使用历史遗留的、非标准的编译器也成为滋生不良编程习惯、阻碍标准推广的源泉。究其原因，某些教师的惰性是一方面，而相关资格考试与现行标准、业界规范的严重脱节也成为应试教育体制下技术标准难以为师生重视的重要因素。正面的例子又如何呢？北理工计算机学院有一门程序设计实践与方法的本科课程，这门课的重心本是算法与编程技巧的训练，但由于它使用了基于gcc的Online Judge机制和像ACM-ICPC一样严格、众多的测试用例，使得学生对代码的标准与严格、算法的正确与高效、条件的完全覆盖必须做仔细的考量。我自己上过这门课，也做过这门课的助教，两种角色的体验让我深知师生各自的抱怨与苦衷。错误究竟在谁？我想不在于这门课的教师和学生，而在于这门课之外的计算机教育氛围。改变这个氛围并不可以一蹴而就，但至少有两点是可以尽己所能的：主观方面，建立标准意识，培养良好的编程习惯；客观方面，使用实现了符合现行标准的编译器。尽管这对计算机教学的整体环境影响甚微，但起码对未来从事计算机方面的工作来说是有益的——实际工程对标准的要求非同儿戏。 原文链接：http://blog.linjian.org/articles/compiler-standard-strict-smart/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章C和C++处理register关键字的一处差异一道 C 语言指针访存题目的引申《程序员的自我修养》是有学习价值的sizeof的一些牛角尖问题小bug引起的C程序32位-64位移植问题]]></description>
			<content:encoded><![CDATA[<p>　　最近做的C/C++工程需要在多种平台下编译（下面所说的编译泛指编译、连接等过程）并运行通过，多种不同版本的编译器考验着代码正确性和严格性。经测试，我们的历史代码中存在着这样几类问题，使得它们在VS2008和低版本gcc下编译通过，但在高版本的gcc下编译就会或多或少地报错：<br />
　　1、使用了外部的函数，但没有在本文件中声明或#include对应的头文件。VS2008和gcc3.2在很多情况下可以智能地从一同编译的其它文件或标准库中找到对应的函数并连接，而gcc4.3要求必须声明。<br />
　　2、使用模板时，T::name表示T::name类型还是T类的name成员？即使不使用typename关键字，VS2008和gcc3.2在很多情况下可以智能推断T::name a;中的T::name表示表示一个类型。但在gcc4.1/4.3下必须使用typename T::name来表示T::name类型。<br />
　　3、隐式类型转换。对于函数Func(string&#038;)，在VS2008中可以采用Func(string("new"));来调用，但gcc不能隐式地转换构造函数的返回，需要使用string str("new"); Func(str);来调用。<br />
　　4、一些copy-paste粗心小问题，比如声明类的成员函数时不应该加“类名::”限定。如果加了，VS2008会忽略，而gcc4.1/4.3会报错。<br />
　　5、非标准的库函数，比如VS2008的itoa（_itoa）。如果说上述几条是“严格性”的问题，这个应该算是“正确性”的问题了———个编译器扩展的内容在另一个编译器看来自然是不知所云。当然，为方便起见，使用编译器提供的非标准扩展也是可以的（比如在不完全支持C99的gcc和VC版本下，分别使用atoq和_atoi64来代替C99的atoll），但这时候别忘了使用条件编译，针对不同的编译器提供不同的代码。<br />
　　等等。<br />
　　总体来看，我们用到的几个编译器的严格程度如下。注意仅仅是从一些小的侧面反映的结果，不完全准确，说明大致情况而已。<br />
<span style="font-family: 宋体;"><br />
　　───────　更严格　──────→<br />
　　 VS2008　　gcc3.2　　gcc4.1　　gcc4.3<br />
　　←──────　更智能　───────<br />
</span></p>
<p>　　编译器的严格性也许是一把双刃剑。但我个人认为严格一些还是利大于弊的。拿上面的问题1来说，我就遇到过<a href="http://blog.linjian.org/articles/32-64-transplant-bug/">gcc3.2“智能”连接错误</a>的问题，而如果使用gcc4.3，这个bug就可以轻易地被定位，节约大量的调试时间。<br />
　　智能的底线是正确性。如果代码中出现了不规范的写法，编译器给出智能判断并忽略问题的同时，应该给程序员一定的警告信息，让他们核实代码并判断是否需要向标准的方向修改。不过可悲的是编译器的警告往往被程序员们一略而过了。也许正是因为这一点，gcc才变得越发严格，试图和程序员划清责任域，减少扯皮现象？<br />
　　当然，仅仅写出语法上严格的代码，解决了不同编译器下编译通过的问题，我们还不能保证程序运行时语义的正确性。例如程序中存在未初始化的变量，一种编译器的实现是以0填充，恰好与程序员的原意相符；而切换到另一编译器下，未初始化的变量成了随机值，程序运行就会出问题。所以，对C/C++这样高度自由的语言来说，严格的编译器也不是万能的，人的因素始终是第一位的。<br />
　　说了这么些严格和智能，评判它们的依据是什么？自然是语言的标准了（C99、ISO C++之类）。有关编译器对语言（包含库）标准的支持，我们大约可以按以下维度划分：<br />
<span style="font-family: 宋体;"><br />
　　　　　　标准要求实现的<br />
　　　　　　　　　↑<br />
　　　　　　　　　│<br />
　　标准未定义的　│　标准定义的<br />
　　　　　←───┼───→<br />
　　　　　　　　　│<br />
　　　　　　　　　│<br />
　　　　　　　　　↓<br />
　　　　　　编译器扩展的<br />
</span></p>
<p>　　标准定义的语法在任何编译器下都应该有相同、无歧义的语义。尽管其内部实现可能不同，或是依赖于具体的机器架构，但在标准中总有明确的说明。而标准中也存在大量未定义的语义，这通常是为了让编译器作者放开限制，做更高效、更简单的实现。这类语法在不同的编译器或不同的运行环境下可能产生不同的结果，经典的例子就是<a href="http://www.is.pku.edu.cn/~qzy/technotes/expression2009.pdf">n=m+++m++</a>这种副作用语句的叠加，再如<a href="http://blog.linjian.org/articles/sizeof-interesting-problems/">L'abcd'表示什么</a>的问题。<br />
　　工程中通常应该严格禁用标准未定义的语法，因为机器架构、编译器种类和版本、优化选项、运行时环境等的变化都有可能对程序的运行结果产生不可预知的影响。但有些时候标准未定义的的语法也是有用的，比如为了学习理解编译器的原理和优化算法。另外在一些技巧性游戏，如<a href="http://en.wikipedia.org/wiki/Quine_(computing)">Quine</a>，以及<a href="http://www.ioccc.org/">IOCCC</a>、<a href="http://felicity.iiit.ac.in/~tle/">Time Limit Exceeded</a>这样的hack竞赛中，使用未定义语法的捷径也是未尝不可的。<br />
　　而编译器扩展的语法和库则是一类相对有用的工具。不同于标准未定义的内容，编译器所做的扩展也是有严格定义的，不会产生不可预知的结果。在对代码的迁移性要求不高的场合直接使用并无大碍。如果对代码的迁移性有要求，那就如上文所述使用条件编译来限制其作用范围。毕竟编译器所做的扩展是为了弥补标准库的不足，方便开发者。如果对代码将来的使用范围有明显的预知，就需要权衡是否使用编译器的扩展特性。不过就连<a href="http://www.lupaworld.com/26540/viewspace-46508.html">Linux内核的代码都在使用gcc的扩展语法</a>。<br />
　　在C/C++语言教学中，强调标准是十分必要的。然而市面上的C/C++教材鱼目混珠：那天在师兄的桌上看到的一本国内某知名高校出版的C++教材上，Hello World程序竟有3处明显的问题，不用试验就知道在VS2008或gcc下编译有两处是Error，一处至少是Warning（据说在VC6下可以编译通过）。一些高校计算机教学中普遍使用历史遗留的、非标准的编译器也成为滋生不良编程习惯、阻碍标准推广的源泉。究其原因，某些教师的惰性是一方面，而相关资格考试与现行标准、业界规范的严重脱节也成为应试教育体制下技术标准难以为师生重视的重要因素。正面的例子又如何呢？北理工计算机学院有一门<a href="http://cs.bit.edu.cn/web/?q=node/211">程序设计实践与方法</a>的本科课程，这门课的重心本是算法与编程技巧的训练，但由于它使用了基于gcc的Online Judge机制和像<a href="http://cm2prod.baylor.edu/">ACM-ICPC</a>一样严格、众多的测试用例，使得学生对代码的标准与严格、算法的正确与高效、条件的完全覆盖必须做仔细的考量。我自己上过这门课，也做过这门课的助教，两种角色的体验让我深知<a href="http://hi.baidu.com/soff/blog/item/7d81a4c206feb51a0ef47793.html">师生各自的抱怨与苦衷</a>。错误究竟在谁？我想不在于这门课的教师和学生，而在于这门课之外的计算机教育氛围。改变这个氛围并不可以一蹴而就，但至少有两点是可以尽己所能的：主观方面，建立标准意识，培养良好的编程习惯；客观方面，使用实现了符合现行标准的编译器。尽管这对计算机教学的整体环境影响甚微，但起码对未来从事计算机方面的工作来说是有益的——实际工程对标准的要求非同儿戏。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/">http://blog.linjian.org/articles/compiler-standard-strict-smart/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/c-cpp-register-difference/" title="C和C++处理register关键字的一处差异">C和C++处理register关键字的一处差异</a></li><li><a href="http://blog.linjian.org/articles/c-pointer-alignment-access/" title="一道 C 语言指针访存题目的引申">一道 C 语言指针访存题目的引申</a></li><li><a href="http://blog.linjian.org/articles/bv-link-load-lib/" title="《程序员的自我修养》是有学习价值的">《程序员的自我修养》是有学习价值的</a></li><li><a href="http://blog.linjian.org/articles/sizeof-interesting-problems/" title="sizeof的一些牛角尖问题">sizeof的一些牛角尖问题</a></li><li><a href="http://blog.linjian.org/articles/32-64-transplant-bug/" title="小bug引起的C程序32位-64位移植问题">小bug引起的C程序32位-64位移植问题</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/compiler-standard-strict-smart/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>sizeof的一些牛角尖问题</title>
		<link>http://blog.linjian.org/articles/sizeof-interesting-problems/</link>
		<comments>http://blog.linjian.org/articles/sizeof-interesting-problems/#comments</comments>
		<pubDate>Sun, 15 Feb 2009 08:08:09 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[sizeof]]></category>
		<category><![CDATA[编译器]]></category>

		<guid isPermaLink="false">http://blog.linjian.org/?p=195</guid>
		<description><![CDATA[　　昨天看了一些C语言问题（http://www.gowrikumar.com/c/）以及国内几个Geeks的解答（http://wangcong.org/blog/?p=291，http://www.matrix67.com/blog/archives/429），看来自己的C基础还是可以的，没遇到什么大问题。有时候自己也去分析过IOCCC（http://www.ioccc.org/）的代码，或多或少可以学到一些东西。尽管听起来都像是“‘回’字有四样写法”的那样。 　　说几条我最近想到的问题及其解答，都是关于sizeof的： 　　1、sizeof(i++)之后，i的值会怎样？答案是不变。记得大一初学C语言时想研究一下sizeof与函数有什么区别，得到的结果只是一些语法上的差别；学了汇编之后看看编译器生成的代码，才发现sizeof在编译时直接给定了一个常值，而非在运行时求值。进而又分析过sizeof(表达式)的结果，清楚了类型提升原理。但我之前没有注意过表达式中出现副作用的问题，于是在sizeof(i++)的问题上犹豫了。现在经过查阅资料和实验，结论是：sizeof在大多数情况下是编译时定值的，表达式中的任何副作用（包括有副作用的运算符、函数调用等）都不会发生。这里说“大多数情况”，排除了针对C99的新特性——不定长数组（variable length array）的特例。参考这篇文章（http://rednaxelafx.javaeye.com/blog/225909），如果sizeof运算符的参数是一个不定长数组，则该需要在运行时计算数组长度。 　　2、sizeof('a')的结果是多少？这个要看是在C中还是C++中了。根据C99标准的规定，'a'叫做整型字符常量（integer character constant），为int型，故结果是4（对于32位机器）；而ISO C++规定，'a'叫做字符字面量（character literal），为char型，故结果是1。C强调了'a'的“数”属性，而C++强调了'a'的“字符”属性。 　　3、sizeof('ab')的结果又是多少？'ab'这种语法我以前没有注意到。经查，这叫做“多字节字符常量”（multi-character character constant），它限制在单引号中包含2至4个字节。根据标准，多字节字符常量的语义由编译器的实现决定。在我测试的gcc 4.0和VS2008中，如果int a = 'abcd'，则a == 0x61626364。sizeof('ab') == sizeof('abc') == sizeof('abcd') == 4。 　　4、那么sizeof(L'a')呢？虽然wchar_t是在源代码级可移植的宽字符，但其大小依赖于操作系统或编译器的定义。无论C或C++，wchar_t字面量本身就是wchar_t类型的常量，所以sizeof(L'a')就等于sizeof(wchar_t)。在我在32位Windows和Linux平台下分别为2和4。 　　5、至于sizeof(L'ab')、sizeof(L'中')、sizeof(L'中国')又会如何？宽字符常量的单引号中出现多个字节构成的单个字符（如L'中'）是合法的，对它取sizeof，结果等于具体实现下的sizeof(wchar_t)。但出现多个字节构成的多个字符（如L'ab'、L'中国'）则是没有定义的，编译器可能报错，也可能给出不同的实现。在我测试的gcc4.0和VS2008中，L'abcd'分别返回了0x64和0x61。对它们取sizeof，结果等于具体实现下的sizeof(wchar_t)，但注意这是标准未定义的，不应该确信。 　　这些东西在现实的工程中很少用到，毕竟自己写代码的时候都偏向于保守的、确保清晰而正确的方法。但在求职面试之类的场合，sizeof还是一个比较重要的考点，钻一钻牛角尖是值得的。 原文链接：http://blog.linjian.org/articles/sizeof-interesting-problems/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章一道 C 语言指针访存题目的引申C和C++处理register关键字的一处差异编译器与标准——严格还是智能？分清程序“所在路径”和“执行路径”]]></description>
			<content:encoded><![CDATA[<p>　　昨天看了一些C语言问题（<a href="http://www.gowrikumar.com/c/">http://www.gowrikumar.com/c/</a>）以及国内几个Geeks的解答（<a href="http://wangcong.org/blog/?p=291">http://wangcong.org/blog/?p=291</a>，<a href="http://www.matrix67.com/blog/archives/429">http://www.matrix67.com/blog/archives/429</a>），看来自己的C基础还是可以的，没遇到什么大问题。有时候自己也去分析过IOCCC（<a href="http://www.ioccc.org/">http://www.ioccc.org/</a>）的代码，或多或少可以学到一些东西。尽管听起来都像是“‘回’字有四样写法”的那样。<br />
　　说几条我最近想到的问题及其解答，都是关于sizeof的：<br />
　　1、sizeof(i++)之后，i的值会怎样？答案是不变。记得大一初学C语言时想研究一下sizeof与函数有什么区别，得到的结果只是一些语法上的差别；学了汇编之后看看编译器生成的代码，才发现sizeof在编译时直接给定了一个常值，而非在运行时求值。进而又分析过sizeof(表达式)的结果，清楚了类型提升原理。但我之前没有注意过表达式中出现副作用的问题，于是在sizeof(i++)的问题上犹豫了。现在经过查阅资料和实验，结论是：sizeof在大多数情况下是编译时定值的，表达式中的任何副作用（包括有副作用的运算符、函数调用等）都不会发生。这里说“大多数情况”，排除了针对C99的新特性——不定长数组（variable length array）的特例。参考这篇文章（<a href="http://rednaxelafx.javaeye.com/blog/225909">http://rednaxelafx.javaeye.com/blog/225909</a>），如果sizeof运算符的参数是一个不定长数组，则该需要在运行时计算数组长度。<br />
　　2、sizeof('a')的结果是多少？这个要看是在C中还是C++中了。根据C99标准的规定，'a'叫做整型字符常量（integer character constant），为int型，故结果是4（对于32位机器）；而ISO C++规定，'a'叫做字符字面量（character literal），为char型，故结果是1。C强调了'a'的“数”属性，而C++强调了'a'的“字符”属性。<br />
　　3、sizeof('ab')的结果又是多少？'ab'这种语法我以前没有注意到。经查，这叫做“多字节字符常量”（multi-character character constant），它限制在单引号中包含2至4个字节。根据标准，多字节字符常量的语义由编译器的实现决定。在我测试的gcc 4.0和VS2008中，如果int a = 'abcd'，则a == 0x61626364。sizeof('ab') == sizeof('abc') == sizeof('abcd') == 4。<br />
　　4、那么sizeof(L'a')呢？虽然wchar_t是在源代码级可移植的宽字符，但其大小依赖于操作系统或编译器的定义。无论C或C++，wchar_t字面量本身就是wchar_t类型的常量，所以sizeof(L'a')就等于sizeof(wchar_t)。在我在32位Windows和Linux平台下分别为2和4。<br />
　　5、至于sizeof(L'ab')、sizeof(L'中')、sizeof(L'中国')又会如何？宽字符常量的单引号中出现多个字节构成的单个字符（如L'中'）是合法的，对它取sizeof，结果等于具体实现下的sizeof(wchar_t)。但出现多个字节构成的多个字符（如L'ab'、L'中国'）则是没有定义的，编译器可能报错，也可能给出不同的实现。在我测试的gcc4.0和VS2008中，L'abcd'分别返回了0x64和0x61。对它们取sizeof，结果等于具体实现下的sizeof(wchar_t)，但注意这是标准未定义的，不应该确信。<br />
　　这些东西在现实的工程中很少用到，毕竟自己写代码的时候都偏向于保守的、确保清晰而正确的方法。但在求职面试之类的场合，sizeof还是一个比较重要的考点，钻一钻牛角尖是值得的。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/sizeof-interesting-problems/">http://blog.linjian.org/articles/sizeof-interesting-problems/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/c-pointer-alignment-access/" title="一道 C 语言指针访存题目的引申">一道 C 语言指针访存题目的引申</a></li><li><a href="http://blog.linjian.org/articles/c-cpp-register-difference/" title="C和C++处理register关键字的一处差异">C和C++处理register关键字的一处差异</a></li><li><a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/" title="编译器与标准——严格还是智能？">编译器与标准——严格还是智能？</a></li><li><a href="http://blog.linjian.org/articles/program-path-distinction/" title="分清程序“所在路径”和“执行路径”">分清程序“所在路径”和“执行路径”</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/sizeof-interesting-problems/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>小bug引起的C程序32位-64位移植问题</title>
		<link>http://blog.linjian.org/articles/32-64-transplant-bug/</link>
		<comments>http://blog.linjian.org/articles/32-64-transplant-bug/#comments</comments>
		<pubDate>Sat, 19 Apr 2008 23:35:52 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[gcc]]></category>

		<guid isPermaLink="false">http://10.61.0.60/wordpress/?p=83</guid>
		<description><![CDATA[　　在64位平台上测试原来在32位平台上工作正常的一个Linux C工程，访问某个函数的返回值时总是出现Segmentation fault。经查，是缺少一个extern声明引起的： 　　在A.c中调用了B.c定义的函数f()，该函数返回类型是char *。在A.c中遗漏了对该函数的extern声明。由于在另一个文件中声明过，gcc在符号表中可以找到f()，就按那个声明的地址连接了，所以编译、连接以及函数本身的运行没有出错。但由于没有声明函数原型，所以在A.c中，默认为f()返回了int型： 　　在32位平台上，gcc的int和char *都是4字节，所以工作正常，函数返回值可用。 　　在64位平台上，gcc默认的int是4字节，char *是8字节，函数返回值被截短，地址无效，故出现了Segmentation fault。 原文链接：http://blog.linjian.org/articles/32-64-transplant-bug/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章编译器与标准——严格还是智能？解决 Eclipse SVN 插件（Subversive）内存不足的问题Btrfs 测试结果简述在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 一道 C 语言指针访存题目的引申]]></description>
			<content:encoded><![CDATA[<p>　　在64位平台上测试原来在32位平台上工作正常的一个Linux C工程，访问某个函数的返回值时总是出现Segmentation fault。经查，是缺少一个extern声明引起的：</p>
<p>　　在A.c中调用了B.c定义的函数f()，该函数返回类型是char *。在A.c中遗漏了对该函数的extern声明。由于在另一个文件中声明过，gcc在符号表中可以找到f()，就按那个声明的地址连接了，所以编译、连接以及函数本身的运行没有出错。但由于没有声明函数原型，所以在A.c中，默认为f()返回了int型：</p>
<p>　　在32位平台上，gcc的int和char *都是4字节，所以工作正常，函数返回值可用。<br />
　　在64位平台上，gcc默认的int是4字节，char *是8字节，函数返回值被截短，地址无效，故出现了Segmentation fault。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/32-64-transplant-bug/">http://blog.linjian.org/articles/32-64-transplant-bug/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/compiler-standard-strict-smart/" title="编译器与标准——严格还是智能？">编译器与标准——严格还是智能？</a></li><li><a href="http://blog.linjian.org/articles/subversive-heap-error/" title="解决 Eclipse SVN 插件（Subversive）内存不足的问题">解决 Eclipse SVN 插件（Subversive）内存不足的问题</a></li><li><a href="http://blog.linjian.org/articles/btrfs-test/" title="Btrfs 测试结果简述">Btrfs 测试结果简述</a></li><li><a href="http://blog.linjian.org/articles/dell-new-kernel-problem/" title="在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 ">在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 </a></li><li><a href="http://blog.linjian.org/articles/c-pointer-alignment-access/" title="一道 C 语言指针访存题目的引申">一道 C 语言指针访存题目的引申</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/32-64-transplant-bug/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>C程序中调用Java方法一个潜在的问题</title>
		<link>http://blog.linjian.org/articles/jni-potential-problem/</link>
		<comments>http://blog.linjian.org/articles/jni-potential-problem/#comments</comments>
		<pubDate>Mon, 31 Mar 2008 13:35:06 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[冲突]]></category>

		<guid isPermaLink="false">http://10.61.0.60/wordpress/?p=82</guid>
		<description><![CDATA[　　JNI提供了C程序和Java程序的互操作机制。通常每个进程C进程只能启动一个JVM实例。在工程中，发现对于C程序fork派生的父子进程，可能存在以下潜在的问题： 　　条件： 　　1、C程序中创建了JVM实例。 　　2、C程序fork出子进程。 　　3、子进程中调用Java方法，进行了某些特定操作，如打开了文件流并没有关闭；或new了apache.axis的Service对象；或调用了某些JavaBean的toXml方法。 　　结果： 　　子进程可能会挂起。用strace查看，子进程阻塞在了futex系统调用。 　　futex是Linux的一种同步机制，在2.5.7内核引入。经测试，对于RHEL AS3（Kernel 2.4），此问题偶然发生（也挂起在futex系统调用，暂不知道2.4中为什么有futex）；对于RHEL AS4或Ubuntu 7.10（Kernel 2.6），此问题几乎一定发生。我们猜测是Java中对文件操作的同步机制与futex有冲突，造成了死锁。具体原因有待分析，目前需要尽量避免在使用JNI的程序中进行上述操作。 原文链接：http://blog.linjian.org/articles/jni-potential-problem/，作者：林健。 本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。 (Digital fingerprint: 993d4981d6d552510db9a08493b2dbec)相关文章Featureless Wireless Router + Linux Machine = VPN Wireless Router解决 Eclipse SVN 插件（Subversive）内存不足的问题将海峰五笔码表转换到 iBus 下使用使用智器 V5 实现山寨 MiFi带有特殊符号的 SCIM 五笔码表]]></description>
			<content:encoded><![CDATA[<p>　　JNI提供了C程序和Java程序的互操作机制。通常每个进程C进程只能启动一个JVM实例。在工程中，发现对于C程序fork派生的父子进程，可能存在以下潜在的问题：<br />
　　条件：<br />
　　1、C程序中创建了JVM实例。<br />
　　2、C程序fork出子进程。<br />
　　3、子进程中调用Java方法，进行了某些特定操作，如打开了文件流并没有关闭；或new了apache.axis的Service对象；或调用了某些JavaBean的toXml方法。<br />
　　结果：<br />
　　子进程可能会挂起。用strace查看，子进程阻塞在了futex系统调用。<br />
　　futex是Linux的一种同步机制，在2.5.7内核引入。经测试，对于RHEL AS3（Kernel 2.4），此问题偶然发生（也挂起在futex系统调用，暂不知道2.4中为什么有futex）；对于RHEL AS4或Ubuntu 7.10（Kernel 2.6），此问题几乎一定发生。我们猜测是Java中对文件操作的同步机制与futex有冲突，造成了死锁。具体原因有待分析，目前需要尽量避免在使用JNI的程序中进行上述操作。</p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/jni-potential-problem/">http://blog.linjian.org/articles/jni-potential-problem/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/vpn-wireless-router/" title="Featureless Wireless Router + Linux Machine = VPN Wireless Router">Featureless Wireless Router + Linux Machine = VPN Wireless Router</a></li><li><a href="http://blog.linjian.org/articles/subversive-heap-error/" title="解决 Eclipse SVN 插件（Subversive）内存不足的问题">解决 Eclipse SVN 插件（Subversive）内存不足的问题</a></li><li><a href="http://blog.linjian.org/articles/sunwb-ibus/" title="将海峰五笔码表转换到 iBus 下使用">将海峰五笔码表转换到 iBus 下使用</a></li><li><a href="http://blog.linjian.org/articles/smartq-v5-mifi/" title="使用智器 V5 实现山寨 MiFi">使用智器 V5 实现山寨 MiFi</a></li><li><a href="http://blog.linjian.org/articles/scim-wubi-quwei/" title="带有特殊符号的 SCIM 五笔码表">带有特殊符号的 SCIM 五笔码表</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/jni-potential-problem/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>随机数不随机——一个有趣的错误</title>
		<link>http://blog.linjian.org/articles/random-number-bug/</link>
		<comments>http://blog.linjian.org/articles/random-number-bug/#comments</comments>
		<pubDate>Tue, 25 Dec 2007 02:02:37 +0000</pubDate>
		<dc:creator>Jian Lin</dc:creator>
				<category><![CDATA[计算机科学与编程]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://10.61.0.60/wordpress/?p=114</guid>
		<description><![CDATA[　　今天在编程过程中犯了一个有趣的错误：用C#写了一个BP神经网络，在训练的过程中发现其收敛速度极其地慢。仔细检查与训练相关的函数、参数并单步调试，没有发现什么有问题的地方。最终审查每一个外围函数，终于找到了问题所在——   　　初始化一个神经网络时需要一系列随机的权值，这就需要生成一系列-1~1的随机数。我原先是这样写的：           static public double RandomClamped()         {             Random ran = new Random();             double a = ran.NextDouble();             double b = ran.NextDouble();             return a - b;         }   　　它的错误在于每次运行时都通过当前时钟来重置随机种子。当程序高速运行时，有可能连续几次的随机种子一样，导致连续生成几个相同的数，影响了神经网络的性能。正确的写法可以是：           static private Random ran = new Random();         static public double RandomClamped()         {             double a [...]]]></description>
			<content:encoded><![CDATA[<p>　　今天在编程过程中犯了一个有趣的错误：用C#写了一个BP神经网络，在训练的过程中发现其收敛速度<span style="font-family: 宋体;">极其</span>地慢。仔细检查与训练相关的函<span style="font-family: 宋体;">数、参数并单步调试，没有发现什么有问题的地方。最终审查每一个外围函数，终于找到了问题所在——<br />
 <br />
　　初始化一个神经网络时需要一系列随机的权值，这就需要生成一系列-1~1的随机数。我原先是这样写的：<br />
 <br />
<span>        static public double RandomClamped()<br />
        {<br />
            Random ran = new Random();<br />
            double a = ran.NextDouble();<br />
            double b = ran.NextDouble();<br />
            return a - b;<br />
        }</span><br />
 <br />
　　它的错误在于每次运行时都通过当前时钟来重置随机种子。当程序高速运行时，有可能连续几次的随机种子一样，导致连续生成几个相同</span><span style="font-family: 宋体;">的数，影响了神经网络的性能。正确的写法可以是：<br />
 <br />
<span>        static private Random ran = new Random();<br />
        static public double RandomClamped()<br />
        {<br />
            double a = ran.NextDouble();<br />
            double b = ran.NextDouble();<br />
            return a - b;<br />
        }</span><br />
 <br />
　　这样程序的每个实例使用同一个随机种子，生成的数列便是随机分布了。网络训练收敛速度大为加快。<br />
 <br />
　　有趣的是，在对原程序调试过程中我并没有很快地发现这个问题，因为我将包括随机数在内的很多网络参数输出到一个TextBox控件中，并</span><span style="font-family: 宋体;">在每次输出时都Update窗口的显示，这样便降低了程序的运行速度，使得连续两个随机数的种子（时钟）不一致，生成的随机数列看上去还是</span><span style="font-family: 宋体;">随机的，问题便被悄悄地掩盖了。</span></p>
<hr />原文链接：<a href="http://blog.linjian.org/articles/random-number-bug/">http://blog.linjian.org/articles/random-number-bug/</a>，作者：<a href="http://blog.linjian.org/">林健</a>。
<br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/cn/">知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议</a>进行许可。
<br />(Digital fingerprint: 
 993d4981d6d552510db9a08493b2dbec)<h3  class="related_post_title">相关文章</h3><ul class="related_post"><li><a href="http://blog.linjian.org/articles/subversive-heap-error/" title="解决 Eclipse SVN 插件（Subversive）内存不足的问题">解决 Eclipse SVN 插件（Subversive）内存不足的问题</a></li><li><a href="http://blog.linjian.org/articles/btrfs-test/" title="Btrfs 测试结果简述">Btrfs 测试结果简述</a></li><li><a href="http://blog.linjian.org/articles/dell-new-kernel-problem/" title="在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 ">在 Dell PowerEdge 1950 上安装 Linux 2.6.32-rc8 内核的问题与解决 </a></li><li><a href="http://blog.linjian.org/articles/dropbear-pam-patch/" title="让 Dropbear 更好地支持 PAM">让 Dropbear 更好地支持 PAM</a></li><li><a href="http://blog.linjian.org/articles/vcardassistant-s60v2/" title="vCard Assistant：将 vCard 导入 Nokia S60v2 手机的辅助工具">vCard Assistant：将 vCard 导入 Nokia S60v2 手机的辅助工具</a></li></ul>]]></content:encoded>
			<wfw:commentRss>http://blog.linjian.org/articles/random-number-bug/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
