<?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>诗意代码 - PoemCode &#187; Software</title>
	<atom:link href="http://www.poemcode.net/category/code/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.poemcode.net</link>
	<description>These codes, As beautiful as poetry!</description>
	<lastBuildDate>Sun, 05 Feb 2012 13:10:46 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>读CSAPP之RAM、Locality和Cache</title>
		<link>http://www.poemcode.net/2012/02/csapp-ram-locality-cache/</link>
		<comments>http://www.poemcode.net/2012/02/csapp-ram-locality-cache/#comments</comments>
		<pubDate>Thu, 02 Feb 2012 12:03:21 +0000</pubDate>
		<dc:creator>Xu Haojie</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[CSAPP]]></category>

		<guid isPermaLink="false">http://www.poemcode.net/?p=2731</guid>
		<description><![CDATA[<div class="wp-caption alignright" style="width: 80px"><img class="size-full wp-image-521" title="Professional Assembly Language" src="http://www.poemcode.net/wp-content/uploads/2011/09/csapp.jpg" alt="Professional Assembly Language" width="80" height="100" /></div> 
RAM（Random Access Memory）中文译作随机存取存储器，所谓“随机存取”，指的是当存储器中的消息被读取或写入时，所需要的时间与这段信息所在的位置无关。相对的，读取或写入顺序访问（Sequential Access）存储设备中的信息时，其所需要的时间与位置就会有关系[1]，比如磁盘存储器。

RAM 分为 SRAM 与 DRAM 两大类，二者区别在于前者对干扰（光、电）不敏感，用于高速缓存存储器，后者用于主存和显存，以及数码相机和摄像机中的传感器。]]></description>
			<content:encoded><![CDATA[<p><small>今年是离开校园的第六年，这六年来我一直在从事应用软件的设计、开发工作，大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面，久而久之，与技术底层疏远了，诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏，时至今日，向上碰到天花板，向下触到花岗岩。五年是一个契机，趁着下一个五年开始之际，我计划用三个月至半年时想间，重新学习这些知识，以期达到巩固基础，厚积薄发的目的。</p>
<p>本篇是我阅读《Computer System: A Programmer’s Perspective》一书的笔记，该书和与之搭配的《Professional Assembly Language》是我当下阅读计划的一部分。</small></p>
<h2>关于RAM</h2>
<p>RAM（Random Access Memory）中文译作随机存取存储器，所谓“随机存取”，指的是当存储器中的消息被读取或写入时，所需要的时间与这段信息所在的位置无关。相对的，读取或写入顺序访问（Sequential Access）存储设备中的信息时，其所需要的时间与位置就会有关系<a href="#ram_wiki"><sup>[1]</sup></a>，比如磁盘存储器。</p>
<p>RAM 分为 SRAM 与 DRAM 两大类，二者区别在于前者对干扰（光、电）不敏感，用于高速缓存存储器，后者用于主存和显存，以及数码相机和摄像机中的传感器。</p>
<p>DRAM 被分成 d 个超单元，每个超单元都是由 w 个 DRAM 单元组成。DRAM 每次传输 w 位信息。超单元被组织成一个 r 行 c 列的阵列，此时 r*c=d。之所以组织成阵列而不是线性数组，原因之一是降低芯片上地址引脚（pin）的数量，缺点是必须分两次发送地址，增加了访问时间。</p>
<h2>关于局部性</h2>
<p>局部性（locality）有两种：时间局部性（temporal locality）和空间局部性（spatial locality）。在一个具有良好时间局部性的程序中，被引用过一次的存储器位置很可能很快不远的将来再被多次引用。在一个具有良好空间局部性的程序中，如果一个存储器位置被引用了一次，那么程序很可能很快引用其附近的位置。</p>
<p>对于具有步长为k的引用模式的程序，步长越小，空间局部性越好。如果步长为1，则存储器层次结构中所有层次上的缓存都是将数据存储为连续的块。</p>
<p>对于取指令来说，循环具有很好的时间和空间局部性，循环体越小，循环迭代次数越多，局部性越好。重复引用同一个变量的程序有良好的时间局部性。</p>
<h2>关于高速缓存</h2>
<p>高速缓存存储器，作为CPU和主存之间的缓存区域，对应用程序性能的影响最大。其结构可以用元组（S，E，B，m）来描述，其大小 C 指的是所有块的大小的和，标记位和有效位不包含在内，因此，C=S*K*B。S=2s，即组数；E 是每个组的行数；B=2b 是块大小。</p>
<p>高速缓存的结构将 m 个地址位分成了 t 个标记位、s 个组索引位和 b 个块偏移位。组索引位告诉字存储在哪个组内；标记位告诉字存储在这个组的哪一行里，只有该行的标记位和地址中的标记位相匹配，并且有效位被设置，才能确定该行确实包含了该字；块偏移位则告诉在 B 个字节的数据块中的字偏移。</p>
<p>块是一个固定大小的信息包，在高速缓存和主存之间来回传送。行是高速缓存中存储块以及其他信息（例如标记位和有效位）的容器。组是一个或多个行的集合。直接映射高速缓存的的组只有一行。全相联高速缓存中组只有一个。</p>
<p>如果每一组里只有一行，即 E=1，那么这个高速缓存被称为直接映射高速缓存（direct-mapped cache）。如果每一组里有多个行，即 1&lt; E&lt;C/B，那么这个高速缓存被称为组相连高速缓存（set associative cache）。如果一个组包含了所有行，即 E=C/B，那么这个高速缓存被称为全相连高速缓存（fully associative cache）。</p>
<p>高速缓存确定一个请求是否被命中，然后抽取出被请求的字的过程，可分为三步：组选择、行匹配、字抽取。</p>
<p>最不常使用（Least-Frequently-Used，LFU）策略会替换在过去某个时间窗口内引用次数最少的那一行，最近最少使用（Least-Recently-Used，LRU）策略会替换最后一次访问时间最久远的一行。</p>
<p>全相联高速缓存只适合做小的高速缓存，例如虚拟存储器系统中的翻译备用缓冲器（TLB），它缓存页表项。</p>
<p>直写（write-through）就是立即将高速缓存块写回到接邻着的低一层中，简单，但是每次写都会引起总线流量。写回（write-back）就是尽可能地推迟存储器更新，只有当替换算法要驱逐更新过的块时，才将其写回到低一层中，其显著减少了总线流量，但增加了复杂性。</p>
<p>写分配（write-allocate）就是先加载低一层中的块到高速缓存中，然后更新这个高速缓存块。非写分配（non-write-allocate）就是避开高速缓存，直接写到低一层中。</p>
<hr style="margin-top:20px; margin-bottom:20px"/>
<ol>
<li><a href="http://zh.wikipedia.org/zh-cn/随机存取存储器" target="blank">维基百科上关于 RAM 的解释</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.poemcode.net/2012/02/csapp-ram-locality-cache/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读CSAPP之字节序列、数和转换</title>
		<link>http://www.poemcode.net/2011/11/csapp-1/</link>
		<comments>http://www.poemcode.net/2011/11/csapp-1/#comments</comments>
		<pubDate>Mon, 07 Nov 2011 08:23:54 +0000</pubDate>
		<dc:creator>Xu Haojie</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[CSAPP]]></category>

		<guid isPermaLink="false">http://www.poemcode.net/?p=2567</guid>
		<description><![CDATA[<div class="wp-caption alignright" style="width: 80px"><img class="size-full wp-image-521" title="Professional Assembly Language" src="http://www.poemcode.net/wp-content/uploads/2011/09/csapp.jpg" alt="Professional Assembly Language" width="80" height="100" /></div> 

计算机系统的一个基本概念就是，从机器的角度来看，程序仅仅只是字节序列，机器没有关于初始源程序的任何信息。

在几乎所有的机器上，多字节对象都被存储为连续的字节序列，对象的地址为所使用字节中最小的字节。但是多个字节如何排布则有两种不同的方式。最低有效字节在最前面的方式，叫做小端法。最高有效字节在最前面的方式，叫做大端法。

绝大多数时候，字节顺序不关紧要，但是有三种情形必须考虑，首先，在不同类型的机器之间通过网络传送二进制数据；其次，反编译代码，阅读表示整数数据的字节序列；还有，在C语言中使用强制类型转换。]]></description>
			<content:encoded><![CDATA[<p><small>今年是离开校园的第五年，这五年来我一直在从事应用软件的设计、开发工作，大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面，久而久之，与技术底层疏远了，诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏，时至今日，向上碰到天花板，向下触到花岗岩。五年是一个契机，趁着下一个五年开始之际，我计划用三个月至半年时想间，重新学习这些知识，以期达到巩固基础，厚积薄发的目的。</p>
<p>本篇是我阅读《Computer System: A Programmer’s Perspective》一书的笔记，该书和与之搭配的《Professional Assembly Language》是我当下阅读计划的一部分。</small></p>
<h2>关于字节序列</h2>
<p>计算机系统的一个基本概念就是，从机器的角度来看，程序仅仅只是字节序列，机器没有关于初始源程序的任何信息。</p>
<p>在几乎所有的机器上，多字节对象都被存储为连续的字节序列，对象的地址为所使用字节中最小的字节。但是多个字节如何排布则有两种不同的方式。最低有效字节在最前面的方式，叫做小端法。最高有效字节在最前面的方式，叫做大端法。</p>
<p>绝大多数时候，字节顺序不关紧要，但是有三种情形必须考虑，首先，在不同类型的机器之间通过网络传送二进制数据；其次，反编译代码，阅读表示整数数据的字节序列；还有，在C语言中使用强制类型转换。</p>
<p>在使用ASCII码作为字符码的任何系统上都将得到相同的结果，与字节顺序和字大小规则无关，因此，文本数据比二进制数据具有更强的平台独立性。</p>
<h2>关于整数 VS. 浮点数</h2>
<p>计算机运算中，整数运算满足人们所熟知的真正的数学运算定律，例如乘法的交换律和结合律。但是，浮点数运算，由于表示的精度有限，浮点运算是不可结合的。</p>
<p>整数的表示只能编码一个相对较小的数值范围，但是这种表示是精确的；浮点数的表示可以编码较大的数值范围，但这种表示是近似的。</p>
<p>C语言标准定义了每种数据类型必须能够表示的最小的取值范围，特别注意，它要求正数和负数的取值范围是对称的。但是，C语言并没有规定使用补码来表示有符号整数。</p>
<h2>关于类型转换</h2>
<p>类型转换分为三种情形，两个位数相同的类型之间转换，高位数的类型向低位数的类型转换，低位数的类型向高位数的类型转换。</p>
<p>对大多数C语言的实现而言，处理同样字长的有符号数和无符号数之间相互转换的一般规则是：数值可能会改变，但是位模式不变。换言之，强制类型转换的结果保持位值不变，只是改变了解释这些位的方式。</p>
<p>当执行一个运算时，如果它的一个运算数是有符号的而另一个是无符号的，那么C语言会隐式地将有符号参数强制转换为无符号数，并假设这两个数都是非负的。对于像<和>这样的关系运算符来说，它会导致非直观的结果。</p>
<p>当低位数的类型向高位数的类型转换时，将一个无符号数转换为一个更大的数据类型时，只需要简单地在表示的开头添加0，这种运算称为零扩展。将一个补码数字转换位一个更大的数据类型可以自行符号扩展，规则是在表示中添加最高有效位的值的副本。</p>
<p>把short转换成unsigned，则先改变大小，然后完成从有符号到无符号的转换。</p>
<p>将一个w位的数截断为一个k位数字时，则丢弃高w-k位。</p>
<p>从float或者double转换成int，值将会向零舍入。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.poemcode.net/2011/11/csapp-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读K&amp;R之指针、数组名和数组指针</title>
		<link>http://www.poemcode.net/2011/10/the_c_programming_language_3/</link>
		<comments>http://www.poemcode.net/2011/10/the_c_programming_language_3/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 09:38:51 +0000</pubDate>
		<dc:creator>Xu Haojie</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[C]]></category>

		<guid isPermaLink="false">http://www.poemcode.net/?p=2557</guid>
		<description><![CDATA[<div class="wp-caption alignright" style="width: 80px"><img class="size-full wp-image-521" title="The C Programming Language" src="http://www.poemcode.net/wp-content/uploads/2011/10/The-C-Programming-Language-Dennis-Ritchie-Kernighan-Brian.jpg" alt="The C Programming Language" width="80" height="100" /></div> 
数组名所代表的就是该数组最开始的一个元素的地址。其和指针是不同的，区别在于指针是一个变量，但数组名不是变量。

当把数组名传递给一个函数时，实际上传递的是该数组第一个元素的地址。在被调用函数中，该参数是一个局部变量，因此，数组名参数必须是一个指针，也就是一个存储地址值的变量。

如果p是一个指向数组中某个元素的指针，那么p+=i将对p进行加i的增量运算，使其指向指针p当前所指向的元素之后的第i个元素。对数组元素a[i]的引用也可以写成*(a+i)这种形式。]]></description>
			<content:encoded><![CDATA[<div id="attachment_2501" class="wp-caption alignright" style="width: 185px"><img src="http://www.poemcode.net/wp-content/uploads/2011/10/dennis-ritchie-wikipedia.jpg" alt="" title="Dennis M. Ritchie" width="175" height="200" class="size-full wp-image-2501" /><p class="wp-caption-text">Dennis M. Ritchie</p></div>
<p><small>直到不久前，我都不曾认真地了解过C。大学课堂上学习过C++，在那段不长的时间里，学到的东西一直支撑着我对C的认知。等工作五年后，我重新学习C时，发现自己好生浅薄。就在自己还在读《The C Programming Language》的第二章时，网络上传来了Dennis M. Ritchie过世的消息。大师已逝，他所留下的知识将会继续惠泽着后人。我把阅读《The C Programming Language》所作的笔记整理出来，希望更多的人能够学习到Dennis M. Ritchie所建立的知识，这也算是对大师的一种纪念。</small></p>
<h2>关于指针</h2>
<p>指针是一种保存变量地址的变量，是能够存放一个地址的一组存储单元。</p>
<p>地址运算符&#038;只能应用于内存中的对象，即变量与数组元素，不能作用于表达式、常量或register类型的变量。一元运算符*是间接寻址或间接引用运算符，当它作用于指针时，将访问指针所指向的对象。</p>
<p>指针只能指向某种特定类型的对象，也就是说，每个指针都必须指向某种特定的数据类型。一个例外情况是指向void类型的指针，可以存放指向任何类型的指针，但它不能间接引用其自身。ANSI C使用类型void*代替char *作为通用指针的类型。</p>
<p>在计算p+n时，n将根据p指向的对象的长度按比例缩放，而p指向的对象的长度则取决与p的声明。</p>
<p>有效的指针运算包括相同类型指针之间的赋值运算；指针同整数之间的加法或减法运算；指向相同数组中元素的两个指针间的减法或比较运算；将指针赋值为0或指针与0之间的比较运算。</p>
<p>通常，对指针有意义的初始化值只能是0或者是表示地址的表达式。C语言保证，0永远不是有效的数据地址。</p>
<h2>关于数组名</h2>
<p>数组名所代表的就是该数组最开始的一个元素的地址。其和指针是不同的，区别在于指针是一个变量，但数组名不是变量。</p>
<p>当把数组名传递给一个函数时，实际上传递的是该数组第一个元素的地址。在被调用函数中，该参数是一个局部变量，因此，数组名参数必须是一个指针，也就是一个存储地址值的变量。</p>
<p>如果p是一个指向数组中某个元素的指针，那么p+=i将对p进行加i的增量运算，使其指向指针p当前所指向的元素之后的第i个元素。对数组元素a[i]的引用也可以写成*(a+i)这种形式。</p>
<h2>关于数组指针</h2>
<p>K&#038;R在第五章中并没有提到数组指针，这里说的数组指针是指向数组的指针的意思，其和指针数组在定义形式上有细微的区别：</p>

<div class="wp_syntax"><div class="code"><pre class="c" style="font-family:monospace;"><span style="color: #993333;">int</span> <span style="color: #339933;">*</span>pa<span style="color: #009900;">&#91;</span><span style="color: #0000dd;">13</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* pa: array[13] of pointer to int */</span>
<span style="color: #993333;">int</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">*</span>p<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #0000dd;">13</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* p: pointer to array[13] of int */</span></pre></div></div>

<p>这里，前者是一个数组，后者是一个指针。看下面一道朋友发来的一道练习题，原始出处见<a href="http://blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge" target="blank">The Ksplice Pointer Challenge</a>。</p>

<div class="wp_syntax"><div class="code"><pre class="c" style="font-family:monospace;"><span style="color: #339933;">#include &lt;stdio.h&gt;</span>
<span style="color: #993333;">int</span> main<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #993333;">int</span> x<span style="color: #009900;">&#91;</span><span style="color: #0000dd;">5</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
  <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;%p<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">,</span> x<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;%p<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">,</span> x<span style="color: #339933;">+</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;%p<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>x<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;%p<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>x<span style="color: #339933;">+</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #b1b100;">return</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>这是题目假定x的内存地址是0x7fffdfbf7f00（64位平台），不上机运行代码，看能做对几个。</p>
<p>第一个问题显然是考察数组名即数组第一个元素的地址这么个知识点，第二个问题则是考察数组地址运算，其中的1代表了一个数组元素长度，即sizeof(int)。</p>
<p>第三个问题我就迷糊了，如果x是一个指针，那么&#038;x取得的值和x的值相同，即&#038;x==x成立，那么x所指向值又是什么？此时，我的糊涂就在于把数组名和指针等同了起来。&#038;是地址运算符，&#038;x就是取得x的地址，明白这一点，那么答案就清楚了。</p>
<p>第四个问题我更加迷糊，&#038;x+1代表什么意思呢？现在，x是数组，&#038;x则是一个指向长度为5的整数数组的指针。那么&#038;x+1，也就是再加上长度为5的整数数组的长度。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.poemcode.net/2011/10/the_c_programming_language_3/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>读K&amp;R之变量、求值顺序和副作用</title>
		<link>http://www.poemcode.net/2011/10/the_c_programming_language_2/</link>
		<comments>http://www.poemcode.net/2011/10/the_c_programming_language_2/#comments</comments>
		<pubDate>Tue, 18 Oct 2011 09:49:26 +0000</pubDate>
		<dc:creator>Xu Haojie</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[C]]></category>

		<guid isPermaLink="false">http://www.poemcode.net/?p=2515</guid>
		<description><![CDATA[<div class="wp-caption alignright" style="width: 80px"><img class="size-full wp-image-521" title="The C Programming Language" src="http://www.poemcode.net/wp-content/uploads/2011/10/The-C-Programming-Language-Dennis-Ritchie-Kernighan-Brian.jpg" alt="The C Programming Language" width="80" height="100" /></div> 
对变量的命名与符号常量的命名存在一些限制条件，名字是由字母和数字组成的序列，但其第一个字符必须是字母。下划线“_”被看作是字母，通常，变量名使用小写字母，符号常量名全部使用大写字母。

对于内部名而言，至少前31个字符是有效的。函数名和外部变量名包含的字符数目可能少于31，这是因为汇编程序和加载程序可能会使用这些外部名，而语言本身是无法控制加载程序和汇编程序。对于外部名，ANSI标准仅保证前6个字符的唯一性，并且不区分大小写。

任何变量的声明都可以使用const限定符限定。该限定符指定变量的值不能被修改。对于数组而言，const限定符指定数组所有元素的值都不能被修改。

默认情况下，外部变量和静态变量都被初始化为0。未经显式初始化的自动化变量的值为未定义值（即无效值）。
]]></description>
			<content:encoded><![CDATA[<div id="attachment_2501" class="wp-caption alignright" style="width: 185px"><img src="http://www.poemcode.net/wp-content/uploads/2011/10/dennis-ritchie-wikipedia.jpg" alt="" title="Dennis M. Ritchie" width="175" height="200" class="size-full wp-image-2501" /><p class="wp-caption-text">Dennis M. Ritchie</p></div>
<p><small>直到不久前，我都不曾认真地了解过C。大学课堂上学习过C++，在那段不长的时间里，学到的东西一直支撑着我对C的认知。等工作五年后，我重新学习C时，发现自己好生浅薄。就在自己还在读《The C Programming Language》的第二章时，网络上传来了Dennis M. Ritchie过世的消息。大师已逝，他所留下的知识将会继续惠泽着后人。我把阅读《The C Programming Language》所作的笔记整理出来，希望更多的人能够学习到Dennis M. Ritchie所建立的知识，这也算是对大师的一种纪念。</small></p>
<h2>关于变量</h2>
<p>对变量的命名与符号常量的命名存在一些限制条件，名字是由字母和数字组成的序列，但其第一个字符必须是字母。下划线“_”被看作是字母，通常，变量名使用小写字母，符号常量名全部使用大写字母。</p>
<p>对于内部名而言，至少前31个字符是有效的。函数名和外部变量名包含的字符数目可能少于31，这是因为汇编程序和加载程序可能会使用这些外部名，而语言本身是无法控制加载程序和汇编程序。对于外部名，ANSI标准仅保证前6个字符的唯一性，并且不区分大小写。</p>
<p>任何变量的声明都可以使用const限定符限定。该限定符指定变量的值不能被修改。对于数组而言，const限定符指定数组所有元素的值都不能被修改。</p>
<p>默认情况下，外部变量和静态变量都被初始化为0。未经显式初始化的自动化变量的值为未定义值（即无效值）。</p>
<p>形容词external与internal是相对的，internal用于描述定义在函数内部的函数参数和变量，外部变量定义在函数之外，因此可以在许多函数中使用。函数本身是“外部的”。</p>
<p>外部变量的用途表现在它们与内部变量相比具有更大的作用域和更长的生存期。过分依赖外部变量会导致一定的风险，因为它会使程序中的数据关系模糊不清，&#8211;外部变量的值可能被意外地或不经意地需该，而程序的修改又变得十分困难。</p>
<p>如果要在外部变量的定义之前使用该变量，或者外部变量的定义和变量的使用不在同一个源文件中，则必须在相应的变量声明中强制地使用关键字extern。</p>
<p>在一个源程序的所有源文件中，一个外部变量只能在某个文件中定义一次，而其他文件可以通过extern声明来使用它。外部变量的定义中必须指定数组的长度，但extern声明则不一定要指定数组的长度。外部变量的初始化只能出现在其定义中。</p>
<h2>关于求值顺序</h2>
<p>C语言没有指定同一运算符中多个操作数的计算顺序（&#038;&#038;、||、?:和,运算符除外），也没有指定函数各参数的求值顺序。有解释称是为了灵活，但我觉得这是一种混乱，不同编译系统对此作出不同的解读，导致了结果差异，这对移植性是一种破坏。</p>
<p>起初，我并没有意识到这个问题会成为“问题”，直到我看到CSDN里有人贴出<a href="http://topic.csdn.net/u/20111016/16/7e2b3d10-75cb-4b43-b081-81aa224ac8fe.html" target="blank">《C测试，看看你的C语言过关了没》</a>。对于那个问题，我依照Java的规则进行回答，和GCC实际结果正相反，这才让我重视起这个问题。</p>
<p>相较之下，<a href="http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.7" target="blank">The Java Language Specification</a> 给出了清晰的定义：</p>
<blockquote><p>
The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.<a href="#java-spec"><sup>1</sup></a></p></blockquote>
<p>这条规则可以继续细化成四条子规则，由此可以清楚，在Java中同一运算符多个操作数，函数中多个参数，均遵照从左到右的计算顺序：</p>
<ol>
<li>Evaluate Left-Hand Operand First</li>
<li>Evaluate Operands before Operation</li>
<li>Evaluation Respects Parentheses and Precedence</li>
<li>Argument Lists are Evaluated Left-to-Right</li>
</ol>
<h2>关于副作用</h2>
<p>函数调用、嵌套赋值语句、自增与自减运算符都有可能产生“副作用”——在对表达式求值的同时，修改了某些变量的值。</p>
<p>从<cite>ISO/IEC 9899:1999</cite>中可以看到如下描述：</p>
<blockquote><p>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. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluation shall be complete and no side effects of subsequent evaluations shall have taken place.<a href="#c-spec"><sup>2</sup></a></p></blockquote>
<p><abbr title="The C Programming Lanauge">K&#038;R</abbr>在第二章的末尾给出一个示例（如下），并且说<q>ANSI C标准明确规定了所有对参数的副作用都必须在函数调用之前生效，但这对前面介绍的printf函数调用没有什么帮助</q>。</p>

<div class="wp_syntax"><div class="code"><pre class="c" style="font-family:monospace;"><span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;%d, %d<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">,</span> <span style="color: #339933;">++</span>n<span style="color: #339933;">,</span> power<span style="color: #009900;">&#40;</span><span style="color: #0000dd;">2</span><span style="color: #339933;">,</span> n<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* 错 */</span></pre></div></div>

<p>但是，我不理解这里为何<q>对前面介绍的printf函数调用没有什么帮助</q>。特意查了<cite>ISO/IEC 9899:1999</cite>对自增操作符副作用的解释，还是不明白。</p>
<blockquote><p>The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point.</p></blockquote>
<p>这个疑问就暂时留下吧，先记着 <abbr title="MISRA-C:2004 Guidelines for the use of the C language in critical systems">MISRA-C</abbr> 对自增、自减运算符的要求吧：</p>
<blockquote><p>The increment (++) and decrement (&#8211;) should not be mixed with other operators in an expression.<a href="#misra-c"><sup>3</sup></a></p></blockquote>
<p>关于计算顺序和副作用的话题，我分别看到了Java和C两个忠告，作为本篇博文的结尾：</p>
<blockquote><p>
It is recommended that code not rely crucially on this specification. Code is usually clearer when each expression contains at most one side effect, as its outermost operation, and when code does not depend on exactly which exception arises as a consequence of the left-to-right evaluation of expressions.<a href="#java-spec"><sup>1</sup></a></p></blockquote>
<blockquote><p>
在任何一种编程语言中，如果代码的执行结果与求值顺序相关，则都是不好的程序设计风格。<a href="#k&#038;r"><sup>4</sup></a></p></blockquote>
<hr style="margin-top:20px; margin-bottom:20px"/>
<h2>参考文献</h2>
<ol>
<li><a name="java_spec">James Gosing, Bill Joy, Guy Steele, Gilad Bracha. The Java Language Specification (third edition)[M]. Addison-Wesley, 2005.5:414</a></li>
<li><a name="c-spec">ISO/IEC 9899:2000 (E) &#8212; Programming Language &#8212; C, 13</a></li>
<li><a name="misra-c">MISRA-C:2004 Guidelines for the use of the C language in critical systems, 2004.10:56</li>
<li><a name="k&#038;r">Brian Kernighan, Dennis Ritchie. C语言程序设计（第2版）[M]. 徐宝文, 李志, 译. 北京:机械工业出版社, 2004.1:43</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.poemcode.net/2011/10/the_c_programming_language_2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读K&amp;R之幻数、返回值和参数传递</title>
		<link>http://www.poemcode.net/2011/10/the_c_programming_language_1/</link>
		<comments>http://www.poemcode.net/2011/10/the_c_programming_language_1/#comments</comments>
		<pubDate>Sat, 15 Oct 2011 02:08:05 +0000</pubDate>
		<dc:creator>Xu Haojie</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[C]]></category>

		<guid isPermaLink="false">http://www.poemcode.net/?p=2470</guid>
		<description><![CDATA[<div class="wp-caption alignright" style="width: 80px"><img class="size-full wp-image-521" title="The C Programming Language" src="http://www.poemcode.net/wp-content/uploads/2011/10/The-C-Programming-Language-Dennis-Ritchie-Kernighan-Brian.jpg" alt="The C Programming Language" width="80" height="100" /></div> 
在程序中使用“幻数”是个坏习惯，它几乎不能提供任何信息。要避免这个问题，就是使用#define指令把符号名定义为一个特定的字符串。这使我想起来了在《深入Java虚拟机》一书中看到的一段话：
<blockquote>
例如每个class文件必须是以四个同样的字节开始：魔数0xCAFEBABE。这个魔数的用处是让class文件分析器很容易分辨出某个文件有明显问题而加以拒绝。<a href="#java_vm_inside"><sup>1</sup></a></blockquote>

上面的“幻数”与“魔数”应该都是英文术语magic number的翻译，我把它理解成一个有特定意义的数字，这个数字没有名字，没人知道它代表什么意思，因此阅读程序代码的人就可能糊涂。——当然，0xCAFEBABE不会有人去读它，我只是想到了magic number这个词而已。]]></description>
			<content:encoded><![CDATA[<div id="attachment_2501" class="wp-caption alignright" style="width: 185px"><img src="http://www.poemcode.net/wp-content/uploads/2011/10/dennis-ritchie-wikipedia.jpg" alt="" title="Dennis M. Ritchie" width="175" height="200" class="size-full wp-image-2501" /><p class="wp-caption-text">Dennis M. Ritchie</p></div>
<p><small>直到不久前，我都不曾认真地了解过C。大学课堂上学习过C++，在那段不长的时间里，学到的东西一直支撑着我对C的认知。等工作五年后，我重新学习C时，发现自己好生浅薄。就在自己还在读《The C Programming Language》的第二章时，网络上传来了Dennis M. Ritchie过世的消息。大师已逝，他所留下的知识将会继续惠泽着后人。我把阅读《The C Programming Language》所作的笔记整理出来，希望更多的人能够学习到Dennis M. Ritchie所建立的知识，这也算是对大师的一种纪念。</small></p>
<h2>关于Magic Number</h2>
<p>在程序中使用“幻数”是个坏习惯，它几乎不能提供任何信息。要避免这个问题，就是使用#define指令把符号名定义为一个特定的字符串。这使我想起来了在《深入Java虚拟机》一书中看到的一段话：</p>
<blockquote><p>
例如每个class文件必须是以四个同样的字节开始：魔数0xCAFEBABE。这个魔数的用处是让class文件分析器很容易分辨出某个文件有明显问题而加以拒绝。<a href="#java_vm_inside"><sup>1</sup></a></p></blockquote>
<p>上面的“幻数”与“魔数”应该都是英文术语magic number的翻译，我把它理解成一个有特定意义的数字，这个数字没有名字，没人知道它代表什么意思，因此阅读程序代码的人就可能糊涂。——当然，0xCAFEBABE不会有人去读它，我只是想到了magic number这个词而已。</p>
<h2>关于返回值</h2>
<p>通常情况下，main函数返回值为0表示正常结束，非0表示出现异常情况或出错结束条件。</p>
<p>那么对于一般函数，返回值有什么约定俗成的规则吗？我想到了 <a href="http://www.kernel.org/doc/Documentation/CodingStyle" target="blank" title="Linux kernel coding style">Linux kernel coding style</a> 中的两段话：</p>
<blockquote><p>
Functions can return values of many different kinds, and one of the most common is a value indicating whether the function succeeded or failed.  Such a value can be represented as an error-code integer (-Exxx = failure, 0 = success) or a &#8220;succeeded&#8221; boolean (0 = failure, non-zero = success).</p>
<p>Functions whose return value is the actual result of a computation, rather than an indication of whether the computation succeeded, are not subject to this rule.  Generally they indicate failure by returning some out-of-range result.  Typical examples would be functions that return pointers; they use NULL or the ERR_PTR mechanism to report failure.</p></blockquote>
<p>上面的文字按照是否表示执行成功来将返回值分为两大类，前者又区分为需要详细的错误码和只需要成功与否两种情形，后者则是使用NULL或ERR_PTR来标识失败。</p>
<h2>关于参数传递</h2>
<p>所有函数参数都是“通过值”传递，传递给被调用函数的参数值存放在临时变量中，而不是存放在原来的变量中。因此，被调用函数不能直接修改主调用函数中变量的值，而只能修改其私有的临时副本的值。</p>
<p>如果要让被调用函数能够修改主调用函数的变量，那么调用者需要向被调用函数提供设置值的变量的地址，而被调用函数则需要将对应的参数声明为指针类型，并通过它间接访问变量。</p>
<p>当把数组名作为参数时，传递给函数的值时数组起始元素的位置或地址，&#8211;它并不赋值数组元素本身。</p>
<hr style="margin-top:20px; margin-bottom:20px"/>
<h2>参考文献</h2>
<ol>
<li><a name="java_vm_inside">Bill Venners. 深入Java虚拟机（原书第二版）[M]. 曹晓钢, 蒋靖, 译. 北京:机械工业出版社, 2003.9:32</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.poemcode.net/2011/10/the_c_programming_language_1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

