<?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>My Pool &#187; Develop</title>
	<atom:link href="http://www.hlouis.com/category/develop/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.hlouis.com</link>
	<description>Help, I can&#039;t swimming......</description>
	<lastBuildDate>Tue, 04 May 2010 15:27:10 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>CEGUI Font module Hack</title>
		<link>http://www.hlouis.com/develop/cegui-font-module-hack/</link>
		<comments>http://www.hlouis.com/develop/cegui-font-module-hack/#comments</comments>
		<pubDate>Tue, 04 May 2010 15:27:10 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[ui]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/cegui-font-module-hack/</guid>
		<description><![CDATA[不知道这种修改算不算得上Hack，为了能让CEGUI在亚洲系字体上能有更好的表现，需要将他的字体模块的缓冲机制（主要是Freetype字体类型）做一个比较大的修改，主要修改的内容如下：
初始化时间过长
CEGUI在初始化的时候，会便利一个字体中所有的Glyph，并在d_cp_map容器中初始化所有的FontGlyph对象。这个在英文字体下没有什么问题，因为一个字体中大约只有几百Glyph，但是在中文字体中这个值一般是65535个，所以初始化的时间过长。准备废除这个初始化的过程。
CEGUI使用一个bitmap，d_glyphPageLoaded作为一个codepoint有没有载入的标志，在这里废弃这个变量，直接使用d_cp_map容器中有没有对应的FontGlyph对象作为这个codepoint有没有载入的标志。
字体缓冲的贴图占用过多
CEGUI中的字体缓冲有一个算法，就是把字体中的Glyph分页，每个页是256个文字，一旦要渲染一个文字的时候，就把这个文字前后一共256个Glyph都渲染到一张贴图上。对于英文字体来说这样不是问题，一个字体最多渲染5到6张贴图就完事了，而且采用这种策略渲染好的贴图就不需要修改了。但是对于中文字体来说这不是一个好的策略，中文字体中文字个数繁多，而且不是采用使用频率在字体中排列的，你渲染的一个文字的前后255个文字说不定在整个过程中就不会使用得到，如果讨论在极端情况下，一个中文字体一共需要65535/256=256张贴图才可以完整的渲染完，这样我们可怜的显存里面就全部都是文字了，明显不可以接受。
计划使用一个简单的缓冲机制，就是一开始设定好一个字体可以使用多少张缓冲贴图，和每张贴图的尺寸（这里暂定512*512）。这样的策略实现起来比较简单，相对来说修改的代码量也不是很大，缺点是：

CEGUI的贴图接口中并没有锁定或修改已存在的贴图内容，需要自己实现。
原始的贴图生成之后不需要修改了，现在需要经常修改，会有一定的效率损失。

]]></description>
			<content:encoded><![CDATA[<p>不知道这种修改算不算得上Hack，为了能让CEGUI在亚洲系字体上能有更好的表现，需要将他的字体模块的缓冲机制（主要是Freetype字体类型）做一个比较大的修改，主要修改的内容如下：</p>
<h2><strong>初始化时间过长</strong></h2>
<p>CEGUI在初始化的时候，会便利一个字体中所有的Glyph，并在d_cp_map容器中初始化所有的FontGlyph对象。这个在英文字体下没有什么问题，因为一个字体中大约只有几百Glyph，但是在中文字体中这个值一般是65535个，所以初始化的时间过长。准备废除这个初始化的过程。</p>
<p>CEGUI使用一个bitmap，d_glyphPageLoaded作为一个codepoint有没有载入的标志，在这里废弃这个变量，直接使用d_cp_map容器中有没有对应的FontGlyph对象作为这个codepoint有没有载入的标志。</p>
<h2><strong>字体缓冲的贴图占用过多</strong></h2>
<p>CEGUI中的字体缓冲有一个算法，就是把字体中的Glyph分页，每个页是256个文字，一旦要渲染一个文字的时候，就把这个文字前后一共256个Glyph都渲染到一张贴图上。对于英文字体来说这样不是问题，一个字体最多渲染5到6张贴图就完事了，而且采用这种策略渲染好的贴图就不需要修改了。但是对于中文字体来说这不是一个好的策略，中文字体中文字个数繁多，而且不是采用使用频率在字体中排列的，你渲染的一个文字的前后255个文字说不定在整个过程中就不会使用得到，如果讨论在极端情况下，一个中文字体一共需要65535/256=256张贴图才可以完整的渲染完，这样我们可怜的显存里面就全部都是文字了，明显不可以接受。</p>
<p>计划使用一个简单的缓冲机制，就是一开始设定好一个字体可以使用多少张缓冲贴图，和每张贴图的尺寸（这里暂定512*512）。这样的策略实现起来比较简单，相对来说修改的代码量也不是很大，缺点是：</p>
<ol>
<li>CEGUI的贴图接口中并没有锁定或修改已存在的贴图内容，需要自己实现。</li>
<li>原始的贴图生成之后不需要修改了，现在需要经常修改，会有一定的效率损失。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/cegui-font-module-hack/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Lua can&#8217;t do math?</title>
		<link>http://www.hlouis.com/develop/lua-cant-do-math/</link>
		<comments>http://www.hlouis.com/develop/lua-cant-do-math/#comments</comments>
		<pubDate>Thu, 17 Sep 2009 04:36:13 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/lua-cant-do-math/</guid>
		<description><![CDATA[今天把项目中所有使用的脚本从python替换到lua，一切看起来很美，不过最后测试的时候发现一个非常诡异的问题，具体lua语句如下：
print("simple test: ")
print((10000 + 123456789) - 123456789)
程序打印出来的值竟然是10003，顿时被雷到了。虽然我知道lua用的是double作为基础的lua_number，但是人家已经很耐心的教育我们double是双精度类型，不会出现什么四舍五入的问题。于是打开linux上的终端运行lua进行同样的计算，这次得到的是正确的结果：10000。
想想也知道lua不会有这种低级的错误的，要不还不立刻被人骂死，于是开始找自己的问题，首先怀疑是我们的lua嵌入包裹代码的问题，仔细review了一遍代码，觉得不会影响到这么底层的问题。接着开始做实验，发现在服务器上的同样lua代码，运行的结果是正确的。经过无尽的折腾，最后发现了这么一个事实：
在directX的sdk文档里面，D3DCREATE这个条目里面有这么一条：
D3DCREATE_FPU_PRESERVE Set the precision for Direct3D floating-point calculations to the precision used by the calling thread. If you do not specify this flag, Direct3D defaults to single-precision round-to-nearest mode for two reasons:
- Double-precision mode will reduce Direct3D performance.
- Portions of Direct3D assume floating-point unit exceptions are masked; unmasking these [...]]]></description>
			<content:encoded><![CDATA[<p>今天把项目中所有使用的脚本从python替换到lua，一切看起来很美，不过最后测试的时候发现一个非常诡异的问题，具体lua语句如下：</p>
<pre class="brush:js">print("simple test: ")
print((10000 + 123456789) - 123456789)</pre>
<p>程序打印出来的值竟然是10003，顿时被雷到了。虽然我知道lua用的是double作为基础的lua_number，但是人家已经很耐心的教育我们double是双精度类型，不会出现什么四舍五入的问题。于是打开linux上的终端运行lua进行同样的计算，这次得到的是正确的结果：10000。</p>
<p>想想也知道lua不会有这种低级的错误的，要不还不立刻被人骂死，于是开始找自己的问题，首先怀疑是我们的lua嵌入包裹代码的问题，仔细review了一遍代码，觉得不会影响到这么底层的问题。接着开始做实验，发现在服务器上的同样lua代码，运行的结果是正确的。经过无尽的折腾，最后发现了这么一个事实：</p>
<p>在directX的sdk文档里面，D3DCREATE这个条目里面有这么一条：</p>
<blockquote><p><strong>D3DCREATE_FPU_PRESERVE</strong> Set the precision for Direct3D floating-point calculations to the precision used by the calling thread. If you do not specify this flag, Direct3D defaults to single-precision round-to-nearest mode for two reasons:<br />
- Double-precision mode will reduce Direct3D performance.<br />
- Portions of Direct3D assume floating-point unit exceptions are masked; unmasking these exceptions may result in undefined behavior</p></blockquote>
<p>D3D默认在创建设备的时候，会把双精度改成单精度，增加渲染的效率，所以lua也被殃及了，这也算是蝴蝶效应的一个体现吧。。。。。</p>
<p>恶心的问题只要原因找到了解决总是相对容易的，现在有两个方案：</p>
<ol>
<li>将lua_number定义为int，缺点是lua内就不能使用小数</li>
<li>在D3D初始化的时候添加参数，让他不要改变计算精度，缺点是不知道对性能有多大影响</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/lua-cant-do-math/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Get rid of memory bugs</title>
		<link>http://www.hlouis.com/develop/get-rid-of-memory-bugs/</link>
		<comments>http://www.hlouis.com/develop/get-rid-of-memory-bugs/#comments</comments>
		<pubDate>Sun, 30 Aug 2009 13:43:49 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/?p=100</guid>
		<description><![CDATA[大型的C/C++程序做到最后，bug高发的时期过去之后，一般来说会出现的问题都是很妖的。最最常见的现象就是一下几种：

调用栈全部混乱，一堆问号出现在这里，你明明知道自己编译的时候有放入符号，现在却什么信息都看不到 
莫名其妙的调用了一个代码里面没有调用的函数 
重复的free或者delete某块内存，然后崩溃 
部分静态或者全局的数据混乱了，代码里面除了初始化就没啥地方写过他们了 
莫名的某个对象的数据全部混乱了 

 
其实有经验的程序员看到我上面这堆牢骚就知道，归根结底，这些问题都是一个原因：内存混乱。可以直接操作内存是C/C++的效率之源也是一切的罪恶之源。没有个两三年的编程经验，很难深刻的理解内存、指针和引用这些看起来没啥深奥的问题。关于可能出现的内存混乱我大概的归纳一下：

使用没有初始化的内存 
内存越界 
内存泄漏

为了理解如何处理这些情况，我们先稍微总结一下程序的内存的使用会出现在什么地方：

静态，全局内存：静态和全局对象会在main函数之前由libc进行初始化和分配，他们因该会出现在运行的数据段上，一般来说是数据段的开头部分。问题2， 4和5可能由这种内存的混乱引起。 
栈内存：基本上可以认为是局部变量初始化的内存了，在程序入栈的时候分配，出栈的时候丢弃。这种内存混乱可能会引起问题1, 2, 4, 5 
堆内存：由malloc、new分配，由free、delete回收的内存。这种内存混乱的话，会引起2、3、4、5 

基本上想要彻底在机制上解决内存问题，肯定是没有希望的，因为如果这是可行的，那么早就有nb的人做出来，并放到标准里面了。于是他们做出了java。。。。。所以我们现在追求的就是如何能够快速的追踪到问题的源头。
不像是其他的逻辑bug，内存混乱引起的问题，现象是千奇百怪的，而且发生问题的源头往往和现象不在同一个调用栈里面，或者是整个调用栈全部乱掉，让你无从可查。所以必须要使用一些其他手段来帮助我们。根据不同的内存使用种类，我说一下自己的思路，和目前已经有的做法：

堆内存：为什么先从堆开始说起，因为堆上的内存分配和删除是完全控制在我们自己手里面的，说实话，这块东西早就有很多的工具和代码进行跟踪保护和分配了。而且相关的原理基本相通。 

最完整的办法解决这个问题是建立自己的内存管理体系，使用内存池，托管系统的malloc、free或者是new、delete。这样不仅仅可以在犯错的时候我们有机会去监测或者记录。还可以通过内存池来增加系统的效率。 
如果暂时没有成体系的内存管理系统，也可以使用第三方工具，比如非侵入式的valgrin或者是编译到代码里面的efence。这些工具都可以很好的帮你监控报告这些内存的混乱。 
对于内存溢出这个问题，不论是自己的内存管理体系还是第三方的监测工具，基本上都是一个原理。在分配内存的时候多申请一定的字节，比如20个bytes，并将他们初始化为一些特殊的字节。然后去检查这块内存有没有变化。如果有变化就是出现了溢出。最佳的方法是用cpu或者是操作系统提供的内存保护机制，这样一旦有任何的要对这块内存的操作指令，就会立刻break出来，这时再用gdb等的debug工具就可以直接查看调用栈找到元凶。较差的方法是定期或者是析构的时候监测，不过这样效果不好，只能知道到底有没有溢出，或者是哪块内存溢出。到底是谁干的，还是要大海捞针。 


栈内存：栈内存的混乱基本上会死的很直接，不会让问题慢慢的慢慢的变大。不过栈内存的特殊性，会导致他把案发现场的很多痕迹抹去，因为栈混乱了，所以你看到的调用栈完全是混乱的。你不知道出现的原因是什么。

对于这种情况，我暂时使用了一种看起来有点简陋的方法。就是使用一个buffer来手动的存储目前的调用栈。入栈的时候通过宏把函数名加入buffer，退栈的时候通过宏把函数名移除。坏处是增加了入栈出栈的代价，同时要记录的函数需要在开始和结束的时候添加宏。 


静态、全局内存混乱：这块是比较复杂的，特别是出现了这种内存的溢出，他会悄无声息的抹去你很多的全局，静态变量，但是系统依然可以运行，不过很多依赖于这些变量的行为都会变的无法预测，天知道这时候会做出什么变态的事情。同时这种内存的分配是在进入main之前发生的，我们完全无能为力。想要对付这种内存我建议。。。。。

在开发小组中规定。。。。不要使用这种内存。 




]]></description>
			<content:encoded><![CDATA[<p><font face="微软雅黑">大型的C/C++程序做到最后，bug高发的时期过去之后，一般来说会出现的问题都是很妖的。最最常见的现象就是一下几种：</font></p>
<ol>
<li><font face="微软雅黑"><span style="background-color: #ffffff">调用栈全部混乱，一堆问号出现在这里，你明明知道自己编译的时候有放入符号，现在却什么信息都看不到</span> </font></li>
<li><font face="微软雅黑"><span style="background-color: #ffffff">莫名其妙的调用了一个代码里面没有调用的函数</span> </font></li>
<li><font face="微软雅黑"><span style="background-color: #ffffff">重复的free或者delete某块内存，然后崩溃</span> </font></li>
<li><font face="微软雅黑"><span style="background-color: #ffffff">部分静态或者全局的数据混乱了，代码里面除了初始化就没啥地方写过他们了</span> </font></li>
<li><font face="微软雅黑"><span style="background-color: #ffffff">莫名的某个对象的数据全部混乱了</span> </font></li>
</ol>
<p> <span id="more-100"></span>
<p><font face="微软雅黑">其实有经验的程序员看到我上面这堆牢骚就知道，归根结底，这些问题都是一个原因：<strong>内存混乱</strong>。可以直接操作内存是C/C++的效率之源也是一切的罪恶之源。没有个两三年的编程经验，很难深刻的理解内存、指针和引用这些看起来没啥深奥的问题。关于可能出现的内存混乱我大概的归纳一下：</font></p>
<ul>
<li><font face="微软雅黑">使用没有初始化的内存 </font></li>
<li><font face="微软雅黑">内存越界 </font></li>
<li><font face="微软雅黑">内存泄漏</font></li>
</ul>
<p><font face="微软雅黑">为了理解如何处理这些情况，我们先稍微总结一下程序的内存的使用会出现在什么地方：</font></p>
<ul>
<li><font face="微软雅黑">静态，全局内存：静态和全局对象会在main函数之前由libc进行初始化和分配，他们因该会出现在运行的数据段上，一般来说是数据段的开头部分。问题2， 4和5可能由这种内存的混乱引起。 </font></li>
<li><font face="微软雅黑">栈内存：基本上可以认为是局部变量初始化的内存了，在程序入栈的时候分配，出栈的时候丢弃。这种内存混乱可能会引起问题1, 2, 4, 5 </font></li>
<li><font face="微软雅黑">堆内存：由malloc、new分配，由free、delete回收的内存。这种内存混乱的话，会引起2、3、4、5 </font></li>
</ul>
<p><font face="微软雅黑">基本上想要彻底在机制上解决内存问题，肯定是没有希望的，因为如果这是可行的，那么早就有nb的人做出来，并放到标准里面了。于是他们做出了java。。。。。所以我们现在追求的就是如何能够快速的追踪到问题的源头。</font></p>
<p><font face="微软雅黑">不像是其他的逻辑bug，内存混乱引起的问题，现象是千奇百怪的，而且发生问题的源头往往和现象不在同一个调用栈里面，或者是整个调用栈全部乱掉，让你无从可查。所以必须要使用一些其他手段来帮助我们。根据不同的内存使用种类，我说一下自己的思路，和目前已经有的做法：</font></p>
<ul>
<li><font face="微软雅黑">堆内存：为什么先从堆开始说起，因为堆上的内存分配和删除是完全控制在我们自己手里面的，说实话，这块东西早就有很多的工具和代码进行跟踪保护和分配了。而且相关的原理基本相通。 </font>
<ul>
<li><font face="微软雅黑">最完整的办法解决这个问题是建立自己的内存管理体系，使用内存池，托管系统的malloc、free或者是new、delete。这样不仅仅可以在犯错的时候我们有机会去监测或者记录。还可以通过内存池来增加系统的效率。</font> </li>
<li><font face="微软雅黑">如果暂时没有成体系的内存管理系统，也可以使用第三方工具，比如非侵入式的valgrin或者是编译到代码里面的efence。这些工具都可以很好的帮你监控报告这些内存的混乱。</font> </li>
<li><font face="微软雅黑">对于内存溢出这个问题，不论是自己的内存管理体系还是第三方的监测工具，基本上都是一个原理。在分配内存的时候多申请一定的字节，比如20个bytes，并将他们初始化为一些特殊的字节。然后去检查这块内存有没有变化。如果有变化就是出现了溢出。最佳的方法是用cpu或者是操作系统提供的内存保护机制，这样一旦有任何的要对这块内存的操作指令，就会立刻break出来，这时再用gdb等的debug工具就可以直接查看调用栈找到元凶。较差的方法是定期或者是析构的时候监测，不过这样效果不好，只能知道到底有没有溢出，或者是哪块内存溢出。到底是谁干的，还是要大海捞针。</font> </li>
</ul>
</li>
<li><font face="微软雅黑">栈内存：栈内存的混乱基本上会死的很直接，不会让问题慢慢的慢慢的变大。不过栈内存的特殊性，会导致他把案发现场的很多痕迹抹去，因为栈混乱了，所以你看到的调用栈完全是混乱的。你不知道出现的原因是什么。</font>
<ul>
<li><font face="微软雅黑">对于这种情况，我暂时使用了一种看起来有点简陋的方法。就是使用一个buffer来手动的存储目前的调用栈。入栈的时候通过宏把函数名加入buffer，退栈的时候通过宏把函数名移除。坏处是增加了入栈出栈的代价，同时要记录的函数需要在开始和结束的时候添加宏。</font> </li>
</ul>
</li>
<li><font face="微软雅黑">静态、全局内存混乱：这块是比较复杂的，特别是出现了这种内存的溢出，他会悄无声息的抹去你很多的全局，静态变量，但是系统依然可以运行，不过很多依赖于这些变量的行为都会变的无法预测，天知道这时候会做出什么变态的事情。同时这种内存的分配是在进入main之前发生的，我们完全无能为力。想要对付这种内存我建议。。。。。</font>
<ul>
<li><font face="微软雅黑">在开发小组中规定。。。。<strong>不要使用这种内存</strong>。</font> </li>
</ul>
</li>
</ul>
<p><font face="微软雅黑"></font></p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/get-rid-of-memory-bugs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Fix IBus Openoffic and Awesome</title>
		<link>http://www.hlouis.com/develop/linux/fix-ibus-openoffic-and-awesome/</link>
		<comments>http://www.hlouis.com/develop/linux/fix-ibus-openoffic-and-awesome/#comments</comments>
		<pubDate>Wed, 31 Dec 2008 06:44:58 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/?p=92</guid>
		<description><![CDATA[Add following line to soffice start script
# IBUS was confilict with OOO3 under awesomedon&#8217;t know why, but add the blow  
# line, everything OK then~ cheers
OOO_FORCE_DESKTOP=gnome export OOO_FORCE_DESKTOP 
]]></description>
			<content:encoded><![CDATA[<p>Add following line to soffice start script</p>
<blockquote><p># IBUS was confilict with OOO3 under awesomedon&#8217;t know why, but add the blow  <br />
# line, everything OK then~ cheers</p>
<p>OOO_FORCE_DESKTOP=gnome export OOO_FORCE_DESKTOP </p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/fix-ibus-openoffic-and-awesome/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flex SecurityError 2148</title>
		<link>http://www.hlouis.com/develop/flash-window/flex-securityerror-2148/</link>
		<comments>http://www.hlouis.com/develop/flash-window/flex-securityerror-2148/#comments</comments>
		<pubDate>Sat, 08 Dec 2007 12:12:42 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Flash window]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/flash-window/flex-securityerror-2148/</guid>
		<description><![CDATA[Flex SecurityError: Error #2148 While Accessing local XML
So you’ve published your Flex application that accesses local XML, but when you try to run it outside of your Flex Builder folder, you might get a Flash Player Debugger error #2148. I ran into this problem while on a tight deadline, and am reposting here to get [...]]]></description>
			<content:encoded><![CDATA[<h2><a href="http://blog.mandalatv.net/?p=334" rel="bookmark" title="Permanent Link: Flex SecurityError: Error #2148 While Accessing local XML">Flex SecurityError: Error #2148 While Accessing local XML</a></h2>
<p>So you’ve published your Flex application that accesses local XML, but when you try to run it outside of your Flex Builder folder, you might get a Flash Player Debugger error #2148. I ran into this problem while on a tight deadline, and am reposting here to get the word out.</p>
<p>To fix this, you need:</p>
<ol>
<li>to right/control click your Flex Project folder in the Navigator</li>
<li>Select properties</li>
<li>Choose Flex Compiler in the popup window</li>
<li>Add “-use-network=false” under Additional compiler arguments</li>
</ol>
<p><img src="http://blog.mandalatv.net/wp-content/uploads/2007/06/flexproperties.jpg" alt="flexproperties.jpg" id="image333" /></p>
<p>Hopefully, this’ll save someone some time…</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/flash-window/flex-securityerror-2148/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Iptables: How-to Share your internet connection</title>
		<link>http://www.hlouis.com/develop/linux/iptables-how-to-share-your-internet-connection/</link>
		<comments>http://www.hlouis.com/develop/linux/iptables-how-to-share-your-internet-connection/#comments</comments>
		<pubDate>Tue, 20 Nov 2007 02:49:55 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/diary/iptables-how-to-share-your-internet-connection/</guid>
		<description><![CDATA[from  http://www.debuntu.org/iptables-how-to-share-your-internet-connection

   
iptables is a command line tool which allow system administrators to configure Linux packet filtering ruleset.
Using iptables, you are able to tweak packet filtering, Network Address Translation (NAT) and packet mangling which in the end are going to allow you to secure your server, share your Internet connection and log [...]]]></description>
			<content:encoded><![CDATA[<p>from  http://www.debuntu.org/iptables-how-to-share-your-internet-connection<!-- google_ad_section_start --></p>
<p class="ad-auto-inserted" style="margin: 0pt 1em 0.25em 0pt; float: left"><!--adsense: cached--></p>
<p class="adsense"> <script type="text/javascript"><!-- google_alternate_ad_url = "http://www.debuntu.org/altadd/rectangle-300.php"; google_ad_client = "pub-3316878663386307"; google_ad_type = "text_image"; google_ad_channel = "8357962815"; google_ad_width = 336; google_ad_height = 280; google_ad_format = "336x280_as"; google_color_border = "ffffff"; google_color_bg = "FAFCFF"; google_color_link = "0A8FBC"; google_color_url = "008000"; google_color_text = "000000"; //--></script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script></p>
<p><strong><a href="http://netfilter.org/" title="iptables" target="_">iptables</a></strong> is a command line tool which allow system administrators to configure Linux packet filtering ruleset.</p>
<p>Using <strong>iptables</strong>, you are able to tweak <em>packet filtering</em>, <em>Network Address Translation</em> (NAT) and <em>packet mangling</em> which in the end are going to allow you to secure your server, share your Internet connection and log unwanted traffic.</p>
<p><strong>iptables</strong> is not really what we could call an easy to get with tool, but once you know the basis, it won&#8217;t be that scary <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<p>This tutorial will provide a sample script you can use to share your Internet access and will give an overview on how to use <strong>iptables</strong></p>
<h2>1. Introduction</h2>
<p>Most people will be freaked out when you pronounce the name <strong>iptables</strong> because it is not much of an easy to understand software, man page is huge as well as <strong>iptables</strong> capabilities.<br />
To be able to set up a home router, you don&#8217;t actually need to spend nights and nights going through iptables man page, a grasp of the basis is enough to get your firewall up and running.</p>
<p>This tutorial provides a sample script you should be able to use out of the box or at most, changing 2 parameters will be able to get you running.</p>
<h2>2. Iptables</h2>
<p>To be able to understand what the firewall do, there is some basis you need to know. Here I&#8217;m going to go over what make <strong>iptables</strong> handle network packets.</p>
<h3>2.1. Chain Rules</h3>
<p><strong>Iptables</strong> use a set of <em>chain rules</em> to check weather or not a packet should be accepted. By default, there is 3 chains:</p>
<ul>
<li><strong>INPUT</strong>: packet is destinate to the machine running iptables</li>
<li><strong>FORWARD</strong>: packet needs to be forwarded to another machine</li>
<li><strong>OUTPUT</strong>: packet going out of the machine running iptables</li>
</ul>
<p>So when a packet reaches the firewall, the first thing the kernel is going to do is to determine where the packet is going. According to the destination, the kernel will check the packet against the rules of the appropriate chain.</p>
<h3>2.2. Actions (TARGET)</h3>
<p>For each <em>chain</em> we define a list of <em>rules</em> and <em>actions</em> (called <em>targets</em> in iptables&#8217;jargon) to take when a packet match a rule. Main actions are:</p>
<ul>
<li><strong>ACCEPT</strong>: accept the packet <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </li>
<li><strong>REJECT</strong>: discard the packet and inform the source</li>
<li><strong>DROP</strong>: discard the packet but don&#8217;t say anything to the source</li>
</ul>
<p>As soon as a packet has matched a rule, the kernel will apply the action it is said to do and won&#8217;t go further. If the packet did not match any rules, the kernel will use the <span class="tech">default policy</span> defined for that chain.</p>
<p>This beeing said, we can now get into the script.</p>
<p><span id="more-66"></span></p>
<h2>3. Iptables Script</h2>
<p>OK, now that we know the really basis, let see what the script is going to look like.</p>
<p>In this example, I assume that <em>eth0</em> is the interface connected to the Internet, <em>eth1</em> is the one connected to our local network.</p>
<blockquote>
<pre class="file">#!/bin/sh
#
# this script requires iptables package to be
# installed on your machine

# Where to find iptables binary
IPT="/sbin/iptables"

# The network interface you will use
# WAN is the one connected to the internet
# LAN the one connected to your local network
WAN="eth0"
LAN="eth1"
# First we need to clear up any existing firewall rules
# and chain which might have been created
$IPT -F
$IPT -F INPUT
$IPT -F OUTPUT
$IPT -F FORWARD
$IPT -F -t mangle
$IPT -F -t nat
$IPT -X

# Default policies: Drop any incoming packets
# accept the rest.
$IPT -P INPUT DROP
$IPT -P OUTPUT ACCEPT
$IPT -P FORWARD ACCEPT

# To be able to forward traffic from your LAN
# to the Internet, we need to tell the kernel
# to allow ip forwarding
echo 1 &gt; /proc/sys/net/ipv4/ip_forward

# Masquerading will make machines from the LAN
# look like if they were the router
$IPT -t nat -A POSTROUTING -o $WAN -j MASQUERADE

# If you want to allow traffic to specific port to be
# forwarded to a machine from your LAN
# here we forward traffic to an HTTP server to machine 192.168.0.2
#$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 80 -j DNAT --to 192.168.0.2:80
#$IPT -A FORWARD -i $WAN -p tcp  --dport 80 -m state --state NEW -j ACCEPT
# For a whole range of port, use:
#$IPT -t nat -A PREROUTING -i $WAN -p tcp --dport 1200:1300 -j DNAT --to 192.168.0.2
#$IPT -A FORWARD -i $WAN -p tcp  --dport 1200:1300 -m state --state NEW -j ACCEPT

# Do not allow new or invalid connections to reach your internal network
$IPT -A FORWARD -i $WAN -m state --state NEW,INVALID -j DROP

# Accept any connections from the local machine
$IPT -A INPUT -i lo -j ACCEPT
# plus from your local network
$IPT -A INPUT -i $LAN -j ACCEPT

# Here we define a new chain which is going to handle
# packets we don't want to respond to
# limit the amount of logs to 10/min
$IPT -N Firewall
$IPT -A Firewall -m limit --limit 10/minute -j LOG --log-prefix "Firewall: "
$IPT -A Firewall -j DROP

# log those packets and inform the sender that the packet was rejected
$IPT -N Rejectwall
$IPT -A Rejectwall -m limit --limit 10/minute -j LOG --log-prefix "Rejectwall: "
$IPT -A Rejectwall -j REJECT
# use the following instead if you want to simulate that the host is not reachable
# for fun though
#$IPT -A Rejectwall -j REJECT  --reject-with icmp-host-unreachable

# here we create a chain to deal with unlegitimate packets
# and limit the number of alerts to 10/min
# packets will be drop without informing the sender
$IPT -N Badflags
$IPT -A Badflags -m limit --limit 10/minute -j LOG --log-prefix "Badflags: "
$IPT -A Badflags -j DROP

# A list of well known combination of Bad TCP flags
# we redirect those to the Badflags chain
# which is going to handle them (log and drop)
$IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j Badflags
$IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL ALL -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j Badflags
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j Badflags

# Accept certain icmp message, drop the others
# and log them through the Firewall chain
# 0 =&gt; echo reply
$IPT -A INPUT -p icmp --icmp-type 0 -j ACCEPT
# 3 =&gt; Destination Unreachable
$IPT -A INPUT -p icmp --icmp-type 3 -j ACCEPT
# 11 =&gt; Time Exceeded
$IPT -A INPUT -p icmp --icmp-type 11 -j ACCEPT
# 8 =&gt; Echo
# avoid ping flood
$IPT -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT
$IPT -A INPUT -p icmp -j Firewall

# Accept ssh connections from the Internet
$IPT -A INPUT -i $WAN -p tcp --dport 22 -j ACCEPT
# or only accept from a certain ip
#$IPT -A INPUT -i $WAN -s 125.124.123.122 -p tcp --dport 22 -j ACCEPT

# Accept related and established connections
$IPT -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Drop netbios from the outside, no log, just drop
$IPT -A INPUT -p udp --sport 137 --dport 137 -j DROP

# Finally, anything which was not allowed yet
# is going to go through our Rejectwall rule
$IPT -A INPUT -j Rejectwall</pre>
</blockquote>
<h3>3.1. Iptables default settings</h3>
<p>First of all, we define where <strong>iptables</strong> binary is located and to make the script easier to attapt to other situation, we define the interface as <em>WAN</em> and <em>LAN</em>.<br />
So, if your machine uses <em>eth1</em> as the interface connected to the Internet and <em>eth0</em> connected to your local network, simply change:</p>
<blockquote>
<p class="dump"> WAN=&#8221;eth0&#8243;<br />
LAN=&#8221;eth1&#8243;</p></blockquote>
<p>to</p>
<blockquote>
<p class="dump"> WAN=&#8221;eth1&#8243;<br />
LAN=&#8221;eth0&#8243;</p></blockquote>
<p>Then we clean up <strong>iptables</strong> by <span class="tech">flushing</span> all the chain and tables:</p>
<blockquote>
<p class="dump">$IPT -F xxx</p>
</blockquote>
<p>and deleting all the <em>optional user-defined chains</em>:</p>
<blockquote>
<p class="dump">$IPT -X</p>
</blockquote>
<p>Then we define the <strong>default policies</strong>:</p>
<blockquote>
<p class="dump">$IPT -P xxx</p>
</blockquote>
<p>Which is to <strong>DROP</strong> any packet which is destinated to the local machine if they were not accepting, <strong>ACCEPT</strong> any packet which is going out of the local machine or going to/coming from our LAN if they were not discarded yet.</p>
<h3>3.2. To and From Local Network</h3>
<p>Because we want to be able to forward traffic, we need to say so to the kernel. This is what is done by setting <span class="tech">/proc/sys/net/ipv4/ip_forward</span> to 1.</p>
<p>Then, we need to tell the kernel to <em>masquerade</em> all <em>outgoing</em> traffic. This is what is achieved by triggering:</p>
<blockquote>
<p class="dump">$IPT -t nat -A POSTROUTING -o $WAN -j MASQUERADE</p>
</blockquote>
<p>Masquerading has the effect of allowing all computer from your internal network to access the internet. These machines will be seen as if there were the router itself.</p>
<p>Now, suppose you want your apache server on machine 192.168.0.2 to be visible from the outside. You need to tell the firewall to send those packets to machine 192.168.0.2 on port 80, this is what is achieved with:</p>
<blockquote>
<p class="dump">$IPT -t nat -A PREROUTING -i $WAN -p tcp &#8211;dport 80 -j DNAT &#8211;to 192.168.0.2:80</p>
</blockquote>
<p>But then, you also need to accept new connection on that specific port because, as you will see later on, we by default forbid <em>NEW</em> and <em>INVALID</em> connections coming from the outside to be forwarded.<br />
So, for that specific service, we will allow <em>NEW</em> connections (<em>RELATED</em> and <em>ESTABLISHED</em> being allowed by default) to be forwarded:</p>
<blockquote>
<p class="dump">$IPT -A FORWARD -i $WAN -p tcp  &#8211;dport 80 -m state &#8211;state NEW -j ACCEPT</p>
</blockquote>
<p>And now, let discard any <em>NEW</em> and <em>INVALID</em> connections:</p>
<blockquote>
<p class="dump">$IPT -A FORWARD -i $WAN -m state &#8211;state NEW,INVALID -j DROP</p>
</blockquote>
<p>To be able to have your local connection to work properly, you need to accept everything on localhost. This is done with:</p>
<blockquote>
<p class="dump"> # Accept any connections from the local machine<br />
$IPT -A INPUT -i lo -j ACCEPT</p></blockquote>
<p class="warning"> We do not need to use the statement <span class="tech">$IPT -A OUTPUT -i lo -j ACCEPT</span> because the default <strong>OUTPUT policy</strong> is set to <strong>ACCEPT</strong><br />
If your default policy is different, you might have to add this statement</p>
<p>Then, because in our example we trust our local network (not a wise thing to do though), we need to allow any incoming connections from our LAN:</p>
<blockquote>
<p class="dump"># plus from your local network<br />
$IPT -A INPUT -i $LAN -j ACCEPT</p></blockquote>
<h3>3.3. Defining custom chains</h3>
<p>In order to get a easier to maintain <strong>iptables</strong> script, it is handy to define some custom chains, also called <em>user-defined chains</em>. This way, you can gather common actions into 1 chain, then, using our target switch (<em>-j</em>) we will be able to send packets that match specific rules to that target.<br />
In order to create a <em>user-defined chain</em>, we need to use:</p>
<blockquote>
<p class="shell">iptables -N chain_name</p>
</blockquote>
<p>and then simply add rules to that chain using the usual:</p>
<blockquote>
<p class="shell">iptables -A chain_name [rules ...] -j target</p>
</blockquote>
<p>Okie, now that this is explained, we are going to create 3 <em>user-defined chains</em> which are going to <span class="system">log</span> packet matching rules to be sent to this specific chain:</p>
<ul>
<li>Firewall: is going to log packets by prepending &#8220;Firewall: &#8221; and DROP them, as you will see, this will only deal with ICMP</li>
<li>Rejectwall: is going to log packets (prepending &#8220;Rejectwall: &#8220;) that were not accepted my any previous rules</li>
<li>Badflags: is going to log packets which TCP flags are not properly set. Some kind of packets are usually used during attack. (prepending &#8220;Badflags: &#8220;)</li>
</ul>
<p>The bit of code that deals with the chain creation and which append rules to it is:</p>
<blockquote>
<p class="dump"> # Here we define a new chain which is going to handle<br />
# packets we don&#8217;t want to respond to<br />
# limit the amount of logs to 10/min<br />
$IPT -N Firewall<br />
$IPT -A Firewall -m limit &#8211;limit 10/minute -j LOG &#8211;log-prefix &#8220;Firewall: &#8221;<br />
$IPT -A Firewall -j DROP</p>
<p># log those packets and inform the sender that the packet was rejected<br />
$IPT -N Rejectwall<br />
$IPT -A Rejectwall -m limit &#8211;limit 10/minute -j LOG &#8211;log-prefix &#8220;Rejectwall: &#8221;<br />
$IPT -A Rejectwall -j REJECT<br />
# use the following instead if you want to simulate that the host is not reachable<br />
# for fun though<br />
#$IPT -A Rejectwall -j REJECT  &#8211;reject-with icmp-host-unreachable</p>
<p># here we create a chain to deal with unlegitimate packets<br />
# and limit the number of alerts to 10/min<br />
# packets will be drop without informing the sender<br />
$IPT -N Badflags<br />
$IPT -A Badflags -m limit &#8211;limit 10/minute -j LOG &#8211;log-prefix &#8220;Badflags: &#8221;<br />
$IPT -A Badflags -j DROP</p></blockquote>
<p>As you can see, there is a new <strong>target</strong> (<em>action</em>), namely <strong>LOG</strong>. <strong>LOG</strong> is a specific target that logs the packet to /var/log/messages usually. <strong>LOG</strong> is a <em>non-terminating target</em>, this means that the packet is going to continue to the next rule after being logged.<br />
by using the <em>&#8211;log-prefix</em> you can specify what is going to be prepended to your log.</p>
<p>So let&#8217;s take the example of chain &#8220;Firewall&#8221;.<br />
First we create the chain: <em>$IPT -N Firewall</em><br />
Then, we ask the kernel to log the packet and to prepend &#8220;Firewall: &#8221; to the log string. But because we don&#8217;t want our logs to be flooded by such logs, we cap the number of logs related to the Firewall chain to 10/minute: <em>$IPT -A Firewall -m limit &#8211;limit 10/minute -j LOG &#8211;log-prefix &#8220;Firewall: &#8220;</em><br />
Finally, after we logged the packet, we are simply going to DROP it: <em>$IPT -A Firewall -j DROP</em></p>
<h3>3.4. Using those rules</h3>
<p>Creating <em>user-defined chain</em> will now make it easier and faster for us to operate specific actions on packets.<br />
Let&#8217;s go through the block of statements related to bad TCP flags:</p>
<blockquote>
<p class="dump"> # A list of well known combination of Bad TCP flags<br />
# we redirect those to the Badflags chain<br />
# which is going to handle them (log and drop)<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ACK,FIN FIN -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ACK,PSH PSH -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ACK,URG URG -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags FIN,RST FIN,RST -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags SYN,FIN SYN,FIN -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags SYN,RST SYN,RST -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ALL ALL -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ALL NONE -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ALL FIN,PSH,URG -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ALL SYN,FIN,PSH,URG -j Badflags<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ALL SYN,RST,ACK,FIN,URG -j Badflags</p></blockquote>
<p>As you can see, for any of the packet matching a rule, we simply have to send the packet to the &#8220;<em>Badflags</em>&#8221; chain. If we were not using <em>user-defined chains</em>, the first statement would look like:</p>
<blockquote>
<p class="dump"> $IPT -A INPUT -p tcp &#8211;tcp-flags ACK,FIN FIN -m limit &#8211;limit 10/minute -j LOG &#8211;log-prefix &#8220;Badflags: &#8221;<br />
$IPT -A INPUT -p tcp &#8211;tcp-flags ACK,FIN FIN -j DROP</p></blockquote>
<p>So this would be twice as much work :s. Now, imagine you want to change the target from DROP to REJECT, you simply have to edit 1 line instead of 11 <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p class="tip">Those badflags rules are well known combinations of illegitimated TCP flags settings. Normal application should not use those, this is why we can DROP those packets safely.</p>
<p>Now, we are going to allow only a small set of <span class="tech">ICMP</span> packets. In our example, we want our firewall to be able to receive information such as <em>Timeout</em> (type 11), <em>Host unreachable</em> (type 3) and we want it to reply to pings (type 8 ) and get replies to ping initiated from our firewall (type 0).<br />
In order to do this, we ACCEPT any <em>ICMP</em> packets which contains one of the following code type and then pass all other <em>ICMP</em> packets code to our <strong>Firewall</strong> chain.</p>
<blockquote>
<p class="dump"> # Accept certain icmp message, drop the others<br />
# and log them through the Firewall chain<br />
# 0 =&gt; echo reply<br />
$IPT -A INPUT -p icmp &#8211;icmp-type 0 -j ACCEPT<br />
# 3 =&gt; Destination Unreachable<br />
$IPT -A INPUT -p icmp &#8211;icmp-type 3 -j ACCEPT<br />
# 11 =&gt; Time Exceeded<br />
$IPT -A INPUT -p icmp &#8211;icmp-type 11 -j ACCEPT<br />
# 8 =&gt; Echo<br />
# avoid ping flood<br />
$IPT -A INPUT -p icmp &#8211;icmp-type 8 -m limit &#8211;limit 1/second -j ACCEPT<br />
$IPT -A INPUT -p icmp -j Firewall</p></blockquote>
<p class="tip">Note the <span class="tech">-m limit &#8211;limit 1/second</span>, by doing such, our firewall is going to reply to only 1 ping per second, any other ping will be logged (up to 10/min and then DROPped) through the <em>Firewall</em> chain</p>
<h3>3.5. Traffic from the Internet</h3>
<p>After we have dealt with not well formed packets and icmp packets, we should apply some global rules to streams coming from the outside (remember that our default policy for OUTPUT packets is ACCEPT, so we don&#8217;t have to allow those).</p>
<p>The basic idea here is to only allow streams that are related to a previous connection (useful for FTP for instance) or already established.<br />
But, we are going to make one exception for <span class="tech">SSH</span> because we want to be able to connect to our box from the outside.</p>
<p>We achieve this by accepting any ssh packets from the outside and then only connections in state RELATED or ESTABLISHED</p>
<blockquote>
<p class="dump"> # Accept ssh connections from the Internet<br />
$IPT -A INPUT -i $WAN -p tcp &#8211;dport 22 -j ACCEPT<br />
# or only accept from a certain ip<br />
#$IPT -A INPUT -i $WAN -s 125.124.123.122 -p tcp &#8211;dport 22 -j ACCEPT</p>
<p># Accept related and established connections<br />
$IPT -A INPUT -m state &#8211;state RELATED,ESTABLISHED -j ACCEPT</p></blockquote>
<p>Then we are going to DROP silently netbios scan from the outside:</p>
<blockquote>
<p class="dump"> # Drop netbios from the outside, no log, just drop<br />
$IPT -A INPUT -p udp &#8211;sport 137 &#8211;dport 137 -j DROP</p></blockquote>
<p>And finally, REJECT any other packet through our user-defined chain <em>Rejectwall</em>:</p>
<blockquote>
<p class="dump"> $IPT -A INPUT -j Rejectwall</p>
</blockquote>
<h2>4. Using iptables&#8217;script</h2>
<h3>4.1. From the command line</h3>
<p>One way to apply the rules we define, is simply to run the script from the command line like:</p>
<p class="shell">sudo sh /path/to/firewall-script.sh</p>
<p>but this has the bad effect of not being restore on reboot :s, but still, this will be of great help while tweaking up your firewall.</p>
<h3>4.2. Using /etc/rc.local</h3>
<p><strong>/etc/rc.local</strong> is a custom file where you can add scripts to be executed at the end of each multiuser runlevel.</p>
<p>By default, this file only contain <em>exit 0</em>.<br />
In order to have your iptables firewall script executed on reboot, simply add the path to your firewall script before <em>exit 0</em>.</p>
<p>Copy your firewall script file to /etc/firewall-script.sh for instance. Then make it executable:</p>
<blockquote>
<p class="shell">sudo chmod 700 /etc/firewall-script.sh</p>
</blockquote>
<p>Then edit /etc/rc.local and add <em>/etc/firewall-script.sh</em> before exit 0</p>
<p>Next time you are going to reboot, this script is going to be executed and therefore, your firewall set up restored.</p>
<h3>4.3. Using /etc/network/if-up.d/ directory</h3>
<p>This one is a bit more tricky.<br />
Once you are done with setting up your firewall script, you will save it to the <em>iptables format</em> by trigerring:</p>
<p class="shell"> $sudo sh /path/to/firewall/script.sh<br />
$sudo iptables-save &gt; /etc/firewall-iptables.conf</p>
<p>Now, open and edit <em>/etc/network/if-up.d/iptables</em> and make it look like:</p>
<blockquote>
<pre class="file">#!/bin/sh
iptables-restore &lt; /etc/firewall-iptables.conf</pre>
</blockquote>
<p>Then make it executable:</p>
<blockquote>
<p class="shell">sudo chmod +x /etc/network/if-up.d/iptables</p>
</blockquote>
<p>Finally, we need a way to set up <em>/proc/sys/net/ipv4/ip_forward</em> to 1. This can be achieved through <span class="tech">/etc/sysctl.conf</span>.<br />
Simply add the following entry if not already there:</p>
<blockquote>
<p class="dump">net.ipv4.ip_forward=1</p>
</blockquote>
<p>which will set /proc/sys/net/ipv4/ip_forward to 1 next time you reboot.</p>
<p class="tip">We could have also used /etc/firewall-script.sh instead of the <em>iptables-restore</em> trick, but this way, you can see another way to do it</p>
<p>Reboot, your firewall should be up again <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h3>4.4. Once upon a time</h3>
<p>Debian used to have this great <span class="tech">/etc/init.d/iptables</span> init script which allowed you to restore iptables settings on boot up, stop your firewall &#8230;<br />
This script is now gone&#8230; so we have got to do it by ourself now <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> </p>
<h3>4.5. Rescue script</h3>
<p>A handy script to have around is a script that can erase all chains and rules in case you are getting lost with your firewall breakages. The following script will clear up all rules and reset all chain so your firewall will be inactive. I suggest you copy it and keep it somewhere close to you in case of emergency.</p>
<blockquote><p>#!/bin/bash<br />
IPT=&#8217;/sbin/iptables&#8217;</p>
<p>for a in `cat /proc/net/ip_tables_names`; do<br />
${IPT} -F -t $a<br />
${IPT} -X -t $a</p>
<p>if [ $a = nat ]; then<br />
${IPT} -t nat -P PREROUTING ACCEPT<br />
${IPT} -t nat -P POSTROUTING ACCEPT<br />
${IPT} -t nat -P OUTPUT ACCEPT<br />
elif [ $a = mangle ]; then<br />
${IPT} -t mangle -P PREROUTING ACCEPT<br />
${IPT} -t mangle -P INPUT ACCEPT<br />
${IPT} -t mangle -P FORWARD ACCEPT<br />
${IPT} -t mangle -P OUTPUT ACCEPT<br />
${IPT} -t mangle -P POSTROUTING ACCEPT<br />
elif [ $a = filter ]; then<br />
${IPT} -t filter -P INPUT ACCEPT<br />
${IPT} -t filter -P FORWARD ACCEPT<br />
${IPT} -t filter -P OUTPUT ACCEPT<br />
fi</p></blockquote>
<pre class="file">done</pre>
<h2>5. Conclusion</h2>
<p>This tutorial covered <strong>iptables</strong> in order to set up a linux firewall which will share your internet connection amongst computer from your local network.<br />
By explaining <strong>iptables</strong> basis, you should now be able to improve your script so you can allow or disallow specific types of traffic.</p>
<p>This is not the most secure set up though. Best practice would be to set up a policy which disallow all traffic by default and then only allow the traffic you believe that should be permitted.</p>
<p>Finally we went through different ways of recovering iptables setting on reboot.</p>
<p>Hope this helps and will give you enough basis to customize your firewall.</p>
<p class="dump">&nbsp;</p>
<blockquote>
<p class="dump"><a href="http://www.hlouis.com/hlouis_com/wp-content/uploads/2007/11/firewall-simple.txt" title="firewall-simple.txt">firewall-simple.txt<br />
</a><a href="http://www.hlouis.com/hlouis_com/wp-content/uploads/2007/11/firewall-stop.txt" title="firewall-stop.txt">firewall-stop.txt</a><a href="http://www.hlouis.com/hlouis_com/wp-content/uploads/2007/11/firewall-simple.txt" title="firewall-simple.txt"><br />
</a></p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/iptables-how-to-share-your-internet-connection/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How-to get your removable device mounted under an explicit and persistent name</title>
		<link>http://www.hlouis.com/develop/linux/give-removable-device-a-name/</link>
		<comments>http://www.hlouis.com/develop/linux/give-removable-device-a-name/#comments</comments>
		<pubDate>Tue, 20 Nov 2007 02:29:20 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/linux/give-removable-device-a-name/</guid>
		<description><![CDATA[from  http://www.debuntu.org/device-partition-labeling
You might have wondered how comes that your mp3 player is automatically mounted under a nice name like JUKEBOX for instance, while you usb stick simply get a name like USB_BAR and USB_BAR-1&#8230; for its partitions.
This is actually due to hal automatically mounting the device.
This tutorial will show you how to give a label [...]]]></description>
			<content:encoded><![CDATA[<p>from  <a href="http://www.debuntu.org/device-partition-labeling">http://www.debuntu.org/device-partition-labeling</a></p>
<p>You might have wondered how comes that your mp3 player is automatically mounted under a nice name like <em>JUKEBOX</em> for instance, while you usb stick simply get a name like <em>USB_BAR</em> and <em>USB_BAR-1</em>&#8230; for its partitions.<br />
This is actually due to <strong>hal</strong> automatically mounting the device.<br />
This tutorial will show you how to give a label to your partitions in order to have your removable devices mounted under an explicit location such as: <em>/media/red-usb-disk</em> or <em>/media/my-big-fat-partition</em>.</p>
<p>When automatically mounting a device, it happens that <strong>hal</strong> already know about this device, in which case, the device is going to be mounted under, let say for an Ipod, /media/Ipod. But if you have an external hard-drive that you connect through usb, chances are that your external hard-drive partitions are mounted under <em>/media/usbdisk</em>, <em>/media/usbdisk-1</em> and so on.<br />
And actually, partition one might be mounted under usbdisk-1 on day and usbdisk the day after :s.<br />
Imagine you stock all you music on your external hardrive. Today, you hardrive get mounted under /media/usbdisk and you create a playlist. Tomorrow, when you plug you hardrive, your music partition might get mounted under /media/usbdisk-1, you start your music player, this one kept your last playlist in memory, but you simply can&#8217;t replay it because the files have moved from /media/usbdisk to /media/usbdisk-1 <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> .<br />
This is where labeling a partition will become handy.</p>
<p><span id="more-65"></span></p>
<p><a title="mainmenu" name="mainmenu"></a> Here is a menu that will let you jump directly to a specific filesystem type:</p>
<ul>
<li><a href="http://www.debuntu.org/device-partition-labeling#ext">ext2 and ext3</a></li>
<li><a href="http://www.debuntu.org/device-partition-labeling#reiserfs">reiserfs</a></li>
<li><a href="http://www.debuntu.org/device-partition-labeling#vfat">vfat</a></li>
<li><a href="http://www.debuntu.org/device-partition-labeling#xfs">XFS</a>: not tested, need your feedback</li>
<li><a href="http://www.debuntu.org/device-partition-labeling#jfs">JFS</a>: not tested, need your feedback</li>
</ul>
<p>Because linux comes with a whole range of file systems, we are going to need different tools depending on which filesystem you are using.<br />
From now on, I will suppose you know which file system your device is formated to. If you don&#8217;t know yet, simply plug you device in order to get it mounted, the run:</p>
<p class="shell">$ df -T</p>
<p>This will output something like:</p>
<p class="dump"> &#8230;..<br />
&#8230;..<br />
/dev/scd0  iso9660     3011040   3011040         0 100% /media/cdrom0<br />
/dev/sdb1     vfat      244480     20756    223724   9% /media/USBDRIVE</p>
<p>You can find the file system type on the second column: here iso9660 for the cdrom and vfat for the usb disk. The device name is found in the first column: here /dev/scd0 and /dev/sdb1</p>
<p>From now on, we are going to work on device /dev/sdaX, you will have to adjust this in accordance with the device you want to rename. Let&#8217;s get into it now <img src='http://www.hlouis.com/hlouis_com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<h2><a title="ext" name="ext"></a> Ext2 and ext3 file systems</h2>
<p><a href="http://www.debuntu.org/device-partition-labeling#mainmenu">back to file systems menu</a></p>
<p>In order to change the label of an ext2 or ext3 partition, you will need to use: <strong>e2fsprogs</strong> program utilities. If it is not yet install on your computer, run the following command to install it:</p>
<p class="shell">$ sudo apt-get install e2fsprogs</p>
<h3>Retrieving the existing label</h3>
<p>In order to retrieve the existing label of your partition, simply run:</p>
<p class="shell">$ sudo e2label /dev/sdaX<br />
my label</p>
<h3>Setting a new label</h3>
<p>To set up a new label, you simply have to append the label name at the end of the command line, so it will look like:</p>
<p class="shell">$ sudo e2label /dev/sdaX &#8220;my new label&#8221;</p>
<p class="important">ext2/3 label has to be at most 16 characters long, if longer, label will be truncated</p>
<p>In order to confirm that your changes where properly applied, you can retrieve the partition&#8217;s label with:</p>
<p class="shell">$ sudo e2label /dev/sdaX<br />
my new label</p>
<p>The new label should be output on the next line.</p>
<h3>Deleting an existing label</h3>
<p>Label can be deleted by supplying an empty label to <strong>e2label</strong> with this command line:</p>
<p class="shell">$ sudo e2label /dev/sdaX &#8220;&#8221;</p>
<h2><a title="reiserfs" name="reiserfs"></a> Reiserfs file system</h2>
<p><a href="http://www.debuntu.org/device-partition-labeling#mainmenu">back to file systems menu</a></p>
<p>First of all, you need to have <strong>reiserfsprogs</strong> package installed. If it not yet present on your machine, please run:</p>
<p class="shell">$ sudo apt-get install reiserfsprogs</p>
<p class="important">While working on a <strong>reiserfs</strong> partition, the partition needs to be unmounted.</p>
<p>Now that you made sure that your partition is <span class="tech">unmounted</span>, let see how it works.</p>
<h3>Retrieving the existing label</h3>
<p>To retrieve the existing label, run:</p>
<p class="shell">$ sudo reiserfstune /dev/sdaX | grep LABEL<br />
&#8230;<br />
LABEL:</p>
<p>The label is going to be apended to &#8220;LABEL: &#8220;, if there is no label yet, only &#8220;LABEL: &#8221; will appear.</p>
<h3>Setting a new label</h3>
<p>To set up a new label, you will need to use the <em>-l</em> switch like:</p>
<p class="shell">$ sudo reiserfstune /dev/sdaX -l &#8220;my new label&#8221;</p>
<p>Check for the &#8220;LABEL: &#8221; entry in the output, this one should now print:</p>
<p class="dump">LABEL: my new label</p>
<p class="important">Reiserfs label has to be at most 16 characters long, if longer, label will be truncated</p>
<h3>Removing an existing label</h3>
<p>To do so, simply supply an empty label with this command line:</p>
<p class="shell">$ sudo reiserfstune /dev/sdaX -l &#8220;&#8221;</p>
<h2><a title="vfat" name="vfat"></a> VFAT file system</h2>
<p><a href="http://www.debuntu.org/device-partition-labeling#mainmenu">back to file systems menu</a></p>
<p>This one is a bit more tricky as you can&#8217;t simply use the command line, but you will need to edit a file in order to let the tool know the name of the device.</p>
<p>The package we are going to use here is <em>mtools</em>. If the package is not installed on your machine, please run:</p>
<p class="shell">$ sudo apt-get install mtools</p>
<h3>Informing mtools about your device</h3>
<p><strong>mtools</strong> needs to be told an &#8220;windows like&#8221; device name (something like G:) to match a linux device name. To do so, create and edit file <span class="tech">~/.mtoolsrc</span> and add:</p>
<pre class="file">drive i: file="/dev/sdaX"</pre>
<p>Where <em>i:</em> is the &#8220;windows name&#8221; and <em>/dev/sdaX</em> is the linux file system associated to it.</p>
<h3>Retrieving the existing label</h3>
<p>Now, you can use <strong>mlabel</strong>, provided by <strong>mtools</strong> package to retrieve the existing label. To do so, trigger the folowin command:</p>
<p class="shell">$ mlabel -s i:</p>
<p>Depending if the device already had a label or not, <strong>mlabel</strong> will output either:</p>
<p class="dump">Volume has no label</p>
<p>or</p>
<p class="dump">Volume label is MY LABEL</p>
<h3>Setting a new label</h3>
<p>Setting a new label is done via the following command line:</p>
<p class="shell">$ mlabel i:&#8221;my new label&#8221;</p>
<h3>Deleting an existing label</h3>
<p>Deleting an existing label can be done with:</p>
<p class="shell">$ mlabel -c i:</p>
<p>Doing so, you won&#8217;t be prompt and the label will be removed.</p>
<h2><a title="xfs" name="xfs"></a> XFS file system</h2>
<p><a href="http://www.debuntu.org/device-partition-labeling#mainmenu">back to file systems menu</a></p>
<p>The tool used here is <strong>xfs_admin</strong> provided by <strong>xfsprogs</strong> package, so in the first place you need to have it installed:</p>
<p class="shell">$ sudo apt-get install xfsprogs</p>
<h3>Retrieving an existing label</h3>
<p>To retrieve an existing label, run:</p>
<p class="shell">$ sudo xfs_admin -l /dev/sdaX</p>
<h3>Setting a new label</h3>
<p>A new label can be set with this command line:</p>
<p class="shell">$ sudo xfs_admin -L &#8220;my new label&#8221; /dev/sdaX</p>
<p class="important">XFS file system can only hold a label of at most 12 characters, if the label supplied is longer, the label will be truncated and a warning will be printed.</p>
<h3>Deleting an existing label</h3>
<p>To delete a label, simply supplied an empty label with this command:</p>
<p class="shell">$ sudo xfs_admin -L &#8220;&#8221; /dev/sdaX</p>
<h2><a title="jfs" name="jfs"></a> JFS file system</h2>
<p><a href="http://www.debuntu.org/device-partition-labeling#mainmenu">back to file systems menu</a></p>
<p>Here, we are going to use <strong>jfs_tune</strong> provided by the <strong>jfsutils</strong> package. Install it with:</p>
<p class="shell">$ sudo apt-get install jfsutils</p>
<h2>Retrieving an existing label</h2>
<p>An existing label can be retrieve with:</p>
<p class="shell">$ sudo jfs_tune -l /dev/sdaX | grep label<br />
Volume label:           &#8216;mylabel&#8217;</p>
<h3>Setting a new label</h3>
<p>A new label can be set with the following command line:</p>
<p class="shell">$ sudo jfs_tune -L &#8220;my new label&#8221; /dev/sdaX</p>
<p class="important">JFS file system can only hold a label of at most 16 characters, if the label supplied is longer, the label will be truncated and a warning will be printed.</p>
<h3>Deleting a label</h3>
<p>A label should be deletable using the following command:</p>
<p class="shell">$ sudo jfs_tune -L &#8220;&#8221; /dev/sdaX</p>
<h2>Conclusion</h2>
<p>By using <em>labels</em>, you will be able to have your removable device mounted under persistent names as well as more user friendly names.<br />
For instance, having to usb stick plugged in, it will be easier for you to manipulate datas on the right device, if one is mounted under /media/blue-usbstick and the other one under /media/red-usbstick instead or /dev/disk1 and /dev/disk2 (mainly when those names can be swapped depending which device you plugged in first).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/give-removable-device-a-name/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How-To: Find files on your computer with find</title>
		<link>http://www.hlouis.com/develop/linux/find-files-on-your-computer-with-find/</link>
		<comments>http://www.hlouis.com/develop/linux/find-files-on-your-computer-with-find/#comments</comments>
		<pubDate>Tue, 20 Nov 2007 02:09:49 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/linux/find-files-on-your-computer-with-find/</guid>
		<description><![CDATA[from Debian/Ubuntu Tips &#38; Tricks


   
A standard Linux system has an incredible amount of files installed. Looking for a file location can be a painful task to do though a file browser.
Fortunately, there is a nifty command line available by default on any Linux distribution: find.
find can virtually find anything on your computer [...]]]></description>
			<content:encoded><![CDATA[<p>from <a href="http://www.debuntu.org/" title="Home">Debian/Ubuntu Tips &amp; Tricks</a></p>
<p><!-- google_ad_section_start --></p>
<p class="ad-auto-inserted" style="margin: 0pt 1em 0.25em 0pt; float: left"><!--adsense: cached--></p>
<p class="adsense"> <script type="text/javascript"><!-- google_alternate_ad_url = "http://www.debuntu.org/altadd/rectangle-300.php"; google_ad_client = "pub-3316878663386307"; google_ad_type = "text_image"; google_ad_channel = "8357962815"; google_ad_width = 336; google_ad_height = 280; google_ad_format = "336x280_as"; google_color_border = "ffffff"; google_color_bg = "FAFCFF"; google_color_link = "0A8FBC"; google_color_url = "008000"; google_color_text = "000000"; //--></script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script></p>
<p>A standard Linux system has an incredible amount of files installed. Looking for a file location can be a painful task to do though a file browser.</p>
<p>Fortunately, there is a nifty command line available by default on any Linux distribution: <strong>find</strong>.</p>
<p><strong>find</strong> can virtually find anything on your computer and comes with a lot of options. This tutorial will introduce a basic use of it and show how you can search your filesystem for file names matching a name pattern.</p>
<p>On Debian based distros, <strong>find</strong> is part of the package <strong>findutils</strong>. <strong>find</strong> allow one to search for files on a filesystem based on different condition, creation date, modified date, file size, file type, permissions, name &#8230;.</p>
<p>In this tutorial, I will be focused on finding files/directories based on their name, in order to explain in more depth the syntax of find, I will also show how you can narrow down your search by adding condition on size and file modification time.</p>
<p>This will suit most searches, if you need more details, I would recommend looking at the find&#8217;s manpage.</p>
<h2>1. Find basis</h2>
<p>The default syntax of <strong>find</strong> is as such:</p>
<blockquote><p> find  [path] [expression]</p></blockquote>
<p>where <em>path</em> is the path used as root for searching pattern and <em>expression</em> the expression we want the file to match.</p>
<h2>2. Finding a file based on filename</h2>
<p>Let say for instance you want to find all .avi files in users home directories. Search files can be found with the following command:</p>
<p class="shell"># find /home -name &#8216;*.avi&#8217;</p>
<p> If you want to search for *.mpg and *.avi files, you will use the following:</p>
<p class="shell">find /home -name &#8216;*.mpg&#8217; -o -name &#8216;*.avi&#8217;</p>
<p>Case insensitive searches can be achieved by using the -iname switch:</p>
<p class="shell">find /home -iname &#8216;*.mpg&#8217; -o -iname &#8216;*.avi&#8217;</p>
<h2>3. Adding some more criterias</h2>
<p>Those kind of searches might returns far too many results, making it hard to find waht you were looking for in the first place.</p>
<p>Fortunately, you can narrow down the search by adding criteria such as the file size and the file modification date.</p>
<p>Let&#8217;search for .avi files bigger than 700M. This can be done with:</p>
<p class="shell">find /home/ -name &#8216;*.avi&#8217; -a -size +700M</p>
<p>Now, let&#8217;s find the same subset of files that were modified less than 15 days ago:</p>
<p class="shell">find /home/ -name &#8216;*.avi&#8217; -a -size +700M -mtime -15</p>
<h2>4. Adding some actions</h2>
<p>Grand, we can now find files based on a subset of criteria. What would be even better is to apply some actions on those files. Action can be done with the use of <em>-exec</em> switch.<br />
We can now find .avi file that are newer that 15 days, in this example, we are going to move those file to another location: /my/new/movies . I consider that this directory already exist on your system.<br />
Moving .avi files bigger than 700M and younger than 15 days to /my/new/movies can be done with:</p>
<p class="shell">find /home/ -name &#8216;*.avi&#8217; -a -size +700M -mtime -15 -exec mv &#8216;{}&#8217; /my/new/movies/ \;</p>
<p>Mind the use of <strong>&#8216;{}&#8217;</strong> and <strong> \;</strong> (there is a space before \;).<br />
<strong>&#8216;{}&#8217;</strong> matches the file that was found, while <strong> \;</strong> terminate the exec statement.</p>
<h2>5. Conclusion</h2>
<p><strong>find</strong> is a powerful tool with an extensive set of statement. This article only covered a small subset of available features. For more information on the find command I recommend checking out its man page.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/find-files-on-your-computer-with-find/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mounting ftp host to local directory on top of FUSE</title>
		<link>http://www.hlouis.com/develop/linux/mounting-ftp-host-to-local-directory-on-top-of-fuse/</link>
		<comments>http://www.hlouis.com/develop/linux/mounting-ftp-host-to-local-directory-on-top-of-fuse/#comments</comments>
		<pubDate>Mon, 19 Nov 2007 05:29:55 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/diary/mounting-ftp-host-to-local-directory-on-top-of-fuse-1-votesnovember-17th-2007-mysurface-posted-in-umount-curlftpfs-mount-sudo-admin-hits-813-i-have-wrote-a-post-regarding/</guid>
		<description><![CDATA[From Linux by Examples
    
I have wrote a post regarding on how to access ftp host using curl. And this time, let us look at how to mount the ftp host to a local directory on top of FUSE.
FUSE (Filesystem in userland) is a userland build on top of virtual filesystem, it [...]]]></description>
			<content:encoded><![CDATA[<p>From <a href="http://linux.byexamples.com/archives/344/mounting-ftp-host-to-local-directory-on-top-of-fuse/#more-344">Linux by Examples</a></p>
<p class="vote-container"> <script type="text/javascript"><!-- google_ad_client = "pub-6971492116812805"; google_ad_width = 336; google_ad_height = 280; google_ad_format = "336x280_as"; google_ad_type = "text"; //2007-11-01: linux top post google_ad_channel = "5689853487"; google_color_border = "FFFFFF"; google_color_bg = "FFFFFF"; google_color_link = "FF6600"; google_color_text = "000000"; google_color_url = "008000"; //--> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script></p>
<p><!-- google_ad_section_start -->I have wrote a post regarding on <a href="http://linux.byexamples.com/archives/320/using-curl-to-access-ftp-server/">how to access ftp host using curl</a>. And this time, let us look at how to mount the ftp host to a local directory on top of FUSE.</p>
<p><a href="http://fuse.sourceforge.net/">FUSE</a> (Filesystem in userland) is a userland build on top of virtual filesystem, it allows you to implement functional filesystem in userspace application. Robson Braga Araujo wrote an app based on fuse and curl that allows you to mount a ftp host to a local directory, <a href="http://curlftpfs.sourceforge.net/">curlftpfs</a>.</p>
<p><strong>What is the benefit of mounting ftp host to a local directory?</strong><br />
The most obvious benefit is easing file management in ftp host. After mounting ftp host to a local dir, you can simply copy, move and delete files using command such as cp, mv, rm. You can easily transfer files from your localhost to ftp host and vice versa.</p>
<p><span id="more-344"></span></p>
<p>To mount ftp host to your local directory, first you need to create a local directory, I created a folder ‘myftp’ and mounting it like this</p>
<pre><code>sudo curlftpfs -o allow_other ftp://myusername:mypassword@ftp.mydomain.com myftp</code></pre>
<p>As simple as that, you are now able to access your ftp host locally.</p>
<p><strong>How to unmount it?</strong><br />
Unmount works as usual.</p>
<pre><code>sudo umount myftp</code></pre>
<p><strong>The command line is lengthy for me, can I auto mount my ftp host by putting it to /etc/fstab ?</strong><br />
Yes, curlftpfs support that.<br />
Inject this line to <em>/etc/fstab</em></p>
<pre><code>curlftpfs#myusername:mypassword@ftp.mydomain.com /mnt/myftp fuse allow_other,rw,user,noauto 0 0</code></pre>
<p>With <em>noauto</em> option, this mount point will not be auto mount when your system restart, you need mount it manually. But this time, you do not need to type the long command line, you now can do this:</p>
<pre><code>sudo mount /mnt/myftp</code></pre>
<p>You may observed that I use <strong>allow_other</strong> in the option, so that I can access /mnt/myftp without need to change myself to root.</p>
<p><strong>Security Issues</strong><br />
Try to run the command line below:</p>
<pre><code>ps aux | grep curlftpfs</code></pre>
<p>OMG! my username and password of my ftp host is visible. I am very sensitive about this, I don’t want it to be so visible, what should I do?</p>
<p>You can create .netrc under root directory and modified the mount line in /etc/fstab.</p>
<p>1. Create /root/.netrc and paste these lines in it.</p>
<pre><code>machine ftp.byexamples.com
login myusername
password mypassword</code></pre>
<p>2. Modified the user mode of the file</p>
<pre><code>sudo chmod o-rw /root/.netrc</code></pre>
<p>3. Modified /etc/fstab</p>
<pre><code>curlftpfs#ftp.mydomain.com /mnt/myftp fuse allow_other,rw,user,noauto 0 0</code></pre>
<p>Although your /root/.netrc is in plain text, but you will need to gain root privilege in order to access the file.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/mounting-ftp-host-to-local-directory-on-top-of-fuse/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>how to ssh without password</title>
		<link>http://www.hlouis.com/develop/linux/how-to-ssh-without-password/</link>
		<comments>http://www.hlouis.com/develop/linux/how-to-ssh-without-password/#comments</comments>
		<pubDate>Tue, 30 Oct 2007 08:01:31 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/how-to-ssh-without-password/</guid>
		<description><![CDATA[from Linux by Examples by mysurface
Search though google, there are a tons of tutorial for accessing a remote site through ssh without a password. I found some tutorial gives too much details. Sometimes, detail’s explanation may confuse the beginners. I just want to make things done, I don’t want to know too much about the [...]]]></description>
			<content:encoded><![CDATA[<p>from Linux by Examples by mysurface</p>
<p>Search though google, there are a tons of tutorial for accessing a remote site through ssh without a password. I found some tutorial gives too much details. Sometimes, detail’s explanation may confuse the beginners. I just want to make things done, I don’t want to know too much about the public key, private key and network authentication’s stuff. Therefore, I am here to provide the clean steps without further explanation .</p>
<p>Let say you want to access to a machine with IP 10.0.0.4, and make sure you have command ssh, ssh-keygen, ssh-copy-id.</p>
<p>First, generate the ‘key’, the key will be used to open the remote machine’s door.</p>
<blockquote><p>ssh-keygen</p></blockquote>
<p>You will see something like that</p>
<p>Enter file in which to save the key (/home/myname/.ssh/id_rsa):</p>
<p>Whatever it appears just press enter until it ends, press enter for passphase as well.</p>
<p>Okay, the ‘key’ will be generated, something looks like ~/.ssh/id_rsa.pub</p>
<p>Copy over the ‘key’ to remote machine, and enter your password</p>
<blockquote><p>ssh-copy-id -i ~/.ssh/id_rsa.pub mysurface@10.0.0.4</p></blockquote>
<p>Done. Now you can ssh 10.0.0.4 with username mysurface without password.</p>
<blockquote><p>ssh mysurface@10.0.0.4</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/linux/how-to-ssh-without-password/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Install FCITX Under Gutsy Gibbon</title>
		<link>http://www.hlouis.com/develop/install-fcitx/</link>
		<comments>http://www.hlouis.com/develop/install-fcitx/#comments</comments>
		<pubDate>Mon, 29 Oct 2007 10:46:00 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.hlouis.com/develop/install-fcitx-under-gutsy-gibbon/</guid>
		<description><![CDATA[在英文环境下安装FCITX没有安装SCIM方便，需要修改一些文件，现在简单列出安装的步骤：

安装FCITX包，直接使用sudo apt-get install fcitx即可，现在源中的文件版本是3.4.3
安装im-switch，直接使用sudo apt-get install im-switch即可
因为是英文环境，所以需要修改Xinput文件：
sudo vi /usr/lib/gtk-2.0/2.10.0/immodule-files.d/libgtk2.0-0.immodules
在该文件中找到
&#8220;xim&#8221; &#8220;X Input Method&#8221; &#8220;gtk20&#8243; &#8220;/usr/share/locale&#8221; &#8220;ko:ja:th:zh&#8221;
替换为：
&#8220;xim&#8221; &#8220;X Input Method&#8221; &#8220;gtk20&#8243; &#8220;/usr/share/locale&#8221; &#8220;en:ko:ja:th:zh&#8221;
im-switch -s fcitx
重新启动Xwindow即可

]]></description>
			<content:encoded><![CDATA[<p>在英文环境下安装FCITX没有安装SCIM方便，需要修改一些文件，现在简单列出安装的步骤：</p>
<ol>
<li>安装FCITX包，直接使用sudo apt-get install fcitx即可，现在源中的文件版本是3.4.3</li>
<li>安装im-switch，直接使用sudo apt-get install im-switch即可</li>
<li>因为是英文环境，所以需要修改Xinput文件：<br />
sudo vi /usr/lib/gtk-2.0/2.10.0/immodule-files.d/libgtk2.0-0.immodules<br />
在该文件中找到<br />
&#8220;xim&#8221; &#8220;X Input Method&#8221; &#8220;gtk20&#8243; &#8220;/usr/share/locale&#8221; &#8220;ko:ja:th:zh&#8221;<br />
替换为：<br />
&#8220;xim&#8221; &#8220;X Input Method&#8221; &#8220;gtk20&#8243; &#8220;/usr/share/locale&#8221; &#8220;en:ko:ja:th:zh&#8221;</li>
<li>im-switch -s fcitx</li>
<li>重新启动Xwindow即可</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/install-fcitx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A string tokenizer</title>
		<link>http://www.hlouis.com/develop/a-string-tokenizer/</link>
		<comments>http://www.hlouis.com/develop/a-string-tokenizer/#comments</comments>
		<pubDate>Fri, 06 Apr 2007 05:13:57 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/a-string-tokenizer/</guid>
		<description><![CDATA[A string tokenizer
A very common operation with strings, is to tokenize it with a delimiter of your own choice. This way you can easily split the string up in smaller pieces, without fiddling with the find() methods too much. In C, you could use strtok() for character arrays, but no equal function exists for strings. [...]]]></description>
			<content:encoded><![CDATA[<p>A string tokenizer</p>
<p>A very common operation with strings, is to tokenize it with a delimiter of your own choice. This way you can easily split the string up in smaller pieces, without fiddling with the find() methods too much. In C, you could use strtok() for character arrays, but no equal function exists for strings. This means you have to make your own. Here is a couple of suggestions, use what suits your best.</p>
<p>The advanced tokenizer:</p>
<p><span id="more-57"></span></p>
<p>void Tokenize(const string&amp; str,<br />
vector<string>&amp; tokens,<br />
const string&amp; delimiters = &#8221; &#8220;)<br />
{<br />
// Skip delimiters at beginning.<br />
string::size_type lastPos = str.find_first_not_of(delimiters, 0);<br />
// Find first &#8220;non-delimiter&#8221;.<br />
string::size_type pos     = str.find_first_of(delimiters, lastPos);</string></p>
<p>while (string::npos != pos || string::npos != lastPos)<br />
{<br />
// Found a token, add it to the vector.<br />
tokens.push_back(str.substr(lastPos, pos &#8211; lastPos));<br />
// Skip delimiters.  Note the &#8220;not_of&#8221;<br />
lastPos = str.find_first_not_of(delimiters, pos);<br />
// Find next &#8220;non-delimiter&#8221;<br />
pos = str.find_first_of(delimiters, lastPos);<br />
}<br />
}</p>
<p>The tokenizer can be used in this way:</p>
<p>#include <string><br />
#include <algorithm><br />
#include <vector></vector></algorithm></string></p>
<p>using namespace std;</p>
<p>int main()<br />
{<br />
vector<string> tokens;</string></p>
<p>string str(&#8220;Split me up! Word1 Word2 Word3.&#8221;);</p>
<p>Tokenize(str, tokens);</p>
<p>copy(tokens.begin(), tokens.end(), ostream_iterator<string>(cout, &#8220;, &#8220;));<br />
}</string></p>
<p>The above code will use the Tokenize function, take the first argument str and split it up. And because we didn&#8217;t supply a third parameter to the function, it will use the default delimiter &#8221; &#8220;, that is &#8211; a whitespace. All elements will be inserted into the vector tokens we created.</p>
<p>In the end we copy() the whole vector to standard out, just to see the contents of the vector on the screen.</p>
<p>Another approach is to let stringstreams do the work. streams in C++ have the special ability, that they read until a whitespace, meaning the following code works if you only want to split on spaces:</p>
<p>#include <vector><br />
#include <string><br />
#include <sstream></sstream></string></vector></p>
<p>using namespace std;</p>
<p>int main()<br />
{<br />
string str(&#8220;Split me by whitespaces&#8221;);<br />
string buf; // Have a buffer string<br />
stringstream ss(str); // Insert the string into a stream</p>
<p>vector<string> tokens; // Create vector to hold our words</string></p>
<p>while (ss &gt;&gt; buf)<br />
tokens.push_back(buf);<br />
}</p>
<p>And that&#8217;s it! The stringstream will use the output operator (&gt;&gt;) and put a string into buf everytime a whitespace is met, buf is then used to push_back() into the vector. And afterwards our vector tokens will contain all the words in str.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/a-string-tokenizer/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>字节对齐</title>
		<link>http://www.hlouis.com/develop/byte-padding/</link>
		<comments>http://www.hlouis.com/develop/byte-padding/#comments</comments>
		<pubDate>Fri, 30 Mar 2007 02:59:10 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/byte-padding/</guid>
		<description><![CDATA[一.什么是字节对齐,为什么要对齐?
现代计算机中内存空间都是按照byte划分的，从理论上讲似乎对任何类型的变量的访问可以从任何地址开始，但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问，这就需要各种类型数据按照一定的规则在空间上排列，而不是顺序的一个接一个的排放，这就是对齐。
对齐的作用和原因：各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况，但是最常见的是如果不按照适合其平台要求对数据存放进行对齐，会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始，如果一个int型（假设为32位系统）如果存放在偶地址开始的地方，那么一个读周期就可以读出这32bit，而如果存放在奇地址开始的地方，就需要2个读周期，并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
二.字节对齐对程序的影响:

先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义：
struct A
{
int a;
char b;
short c;
};
struct B
{
char b;
int a;
short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)
short:2(有符号无符号同)
int:4(有符号无符号同)
long:4(有符号无符号同)
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12
结构体A中包含了4字节长度的int一个，1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐，恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1：
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐，恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.
三.编译器是按照什么样的原则进行对齐的?
先让我们看四个重要的基本概念：
1.数据类型自身的对齐值：
对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，单位字节。
2.结构体或者类的自身对齐值：其成员中自身对齐值最大的那个值。
3.指定对齐值：#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值：自身对齐值和指定对齐值中小的那个值。
有了这些值，我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值，最重要。有效对齐N，就是表示“对齐在N上”，也就是说该数据的&#8221;存放起始地址%N=0&#8243;.而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放，结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍，结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析：
分析例子B；
struct B
{
char b;
int a;
short c;
};
假设B从地址空间0&#215;0000开始排放。该例子中没有定义指定对齐值，在笔者环境下，该值默认为4。第一个成员变量b的自身对齐值是1，比指定或者默认指定对齐值4小，所以其有效对齐值为1，所以其存放地址0&#215;0000符合0&#215;0000%1=0.第二个成员变量a，其自身对齐值为4，所以有效对齐值也为4，所以只能存放在起始地址为0&#215;0004到0&#215;0007这四个连续的字节空间中，复核0&#215;0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2，所以有效对齐值也是2，可以存放在0&#215;0008到0&#215;0009这两个字节空间中，符合0&#215;0008%2=0。所以从0&#215;0000到0&#215;0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b）所以就是4，所以结构体的有效对齐值也是4。根据结构体圆整的要求， 0&#215;0009到0&#215;0000=10字节，（10＋2）％4＝0。所以0&#215;0000A到0&#215;000B也为结构体B所占用。故B从0&#215;0000到0&#215;000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0&#215;0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C：
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short [...]]]></description>
			<content:encoded><![CDATA[<p>一.什么是字节对齐,为什么要对齐?</p>
<p>现代计算机中内存空间都是按照byte划分的，从理论上讲似乎对任何类型的变量的访问可以从任何地址开始，但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问，这就需要各种类型数据按照一定的规则在空间上排列，而不是顺序的一个接一个的排放，这就是对齐。<br />
对齐的作用和原因：各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况，但是最常见的是如果不按照适合其平台要求对数据存放进行对齐，会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始，如果一个int型（假设为32位系统）如果存放在偶地址开始的地方，那么一个读周期就可以读出这32bit，而如果存放在奇地址开始的地方，就需要2个读周期，并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。<br />
二.字节对齐对程序的影响:</p>
<p><span id="more-56"></span></p>
<p>先让我们看几个例子吧(32bit,x86环境,gcc编译器):<br />
设结构体如下定义：<br />
struct A<br />
{<br />
int a;<br />
char b;<br />
short c;<br />
};<br />
struct B<br />
{<br />
char b;<br />
int a;<br />
short c;<br />
};<br />
现在已知32位机器上各种数据类型的长度如下:<br />
char:1(有符号无符号同)<br />
short:2(有符号无符号同)<br />
int:4(有符号无符号同)<br />
long:4(有符号无符号同)<br />
float:4    double:8<br />
那么上面两个结构大小如何呢?<br />
结果是:<br />
sizeof(strcut A)值为8<br />
sizeof(struct B)的值却是12</p>
<p>结构体A中包含了4字节长度的int一个，1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。<br />
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:<br />
#pragma pack (2) /*指定按2字节对齐*/<br />
struct C<br />
{<br />
char b;<br />
int a;<br />
short c;<br />
};<br />
#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br />
sizeof(struct C)值是8。<br />
修改对齐值为1：<br />
#pragma pack (1) /*指定按1字节对齐*/<br />
struct D<br />
{<br />
char b;<br />
int a;<br />
short c;<br />
};<br />
#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br />
sizeof(struct D)值为7。<br />
后面我们再讲解#pragma pack()的作用.<br />
三.编译器是按照什么样的原则进行对齐的?</p>
<p>先让我们看四个重要的基本概念：<br />
1.数据类型自身的对齐值：<br />
对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，单位字节。<br />
2.结构体或者类的自身对齐值：其成员中自身对齐值最大的那个值。<br />
3.指定对齐值：#pragma pack (value)时的指定对齐值value。<br />
4.数据成员、结构体和类的有效对齐值：自身对齐值和指定对齐值中小的那个值。<br />
有了这些值，我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值，最重要。有效对齐N，就是表示“对齐在N上”，也就是说该数据的&#8221;存放起始地址%N=0&#8243;.而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放，结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍，结合下面例子理解)。这样就不能理解上面的几个例子的值了。<br />
例子分析：<br />
分析例子B；<br />
struct B<br />
{<br />
char b;<br />
int a;<br />
short c;<br />
};<br />
假设B从地址空间0&#215;0000开始排放。该例子中没有定义指定对齐值，在笔者环境下，该值默认为4。第一个成员变量b的自身对齐值是1，比指定或者默认指定对齐值4小，所以其有效对齐值为1，所以其存放地址0&#215;0000符合0&#215;0000%1=0.第二个成员变量a，其自身对齐值为4，所以有效对齐值也为4，所以只能存放在起始地址为0&#215;0004到0&#215;0007这四个连续的字节空间中，复核0&#215;0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2，所以有效对齐值也是2，可以存放在0&#215;0008到0&#215;0009这两个字节空间中，符合0&#215;0008%2=0。所以从0&#215;0000到0&#215;0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b）所以就是4，所以结构体的有效对齐值也是4。根据结构体圆整的要求， 0&#215;0009到0&#215;0000=10字节，（10＋2）％4＝0。所以0&#215;0000A到0&#215;000B也为结构体B所占用。故B从0&#215;0000到0&#215;000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0&#215;0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.<br />
同理,分析上面例子C：<br />
#pragma pack (2) /*指定按2字节对齐*/<br />
struct C<br />
{<br />
char b;<br />
int a;<br />
short c;<br />
};<br />
#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br />
第一个变量b的自身对齐值为1，指定对齐值为2，所以，其有效对齐值为1，假设C从0&#215;0000开始，那么b存放在0&#215;0000，符合0&#215;0000%1= 0;第二个变量，自身对齐值为4，指定对齐值为2，所以有效对齐值为2，所以顺序存放在0&#215;0002、0&#215;0003、0&#215;0004、0&#215;0005四个连续字节中，符合0&#215;0002%2=0。第三个变量c的自身对齐值为2，所以有效对齐值为2，顺序存放<br />
在0&#215;0006、0&#215;0007中，符合 0&#215;0006%2=0。所以从0&#215;0000到0&#215;00007共八字节存放的是C的变量。又C的自身对齐值为4，所以C的有效对齐值为2。又8%2=0,C 只占用0&#215;0000到0&#215;0007的八个字节。所以sizeof(struct C)=8.<br />
四.如何修改编译器的默认对齐值?</p>
<p>1.在VC IDE中，可以这样修改：[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改，默认是8字节。<br />
2.在编码时，可以这样动态修改：#pragma pack .注意:是pragma而不是progma.<br />
五.针对字节对齐,我们在编程中如何考虑?</p>
<p>如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员：<br />
struct A{<br />
char a;<br />
char reserved[3];//使用空间换时间<br />
int b;<br />
}</p>
<p>reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.<br />
六.字节对齐可能带来的隐患:</p>
<p>代码中关于对齐的隐患，很多是隐式的。比如在强制类型转换的时候。例如：<br />
unsigned int i = 0&#215;12345678;<br />
unsigned char *p=NULL;<br />
unsigned short *p1=NULL;</p>
<p>p=&amp;i;<br />
*p=0&#215;00;<br />
p1=(unsigned short *)(p+1);<br />
*p1=0&#215;0000;<br />
最后两句代码，从奇数边界去访问unsignedshort型变量，显然不符合对齐的规定。<br />
在x86上，类似的操作只会影响效率，但是在MIPS或者sparc上，可能就是一个error,因为它们要求必须字节对齐.<br />
七.如何查找与字节对齐方面的问题:</p>
<p>如果出现对齐或者赋值问题首先查看<br />
1. 编译器的big little端设置<br />
2. 看这种体系本身是否支持非对齐访问<br />
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。</p>
<p>八.相关文章:转自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx</p>
<p>ARM下的对齐处理<br />
from DUI0067D_ADS1_2_CompLib</p>
<p>3.13 type  qulifiers</p>
<p>有部分摘自ARM编译器文档对齐部分</p>
<p>对齐的使用:<br />
1.__align(num)<br />
这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时<br />
就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。<br />
这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节<br />
对齐,但是不能让4字节的对象2字节对齐。<br />
__align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。</p>
<p>2.__packed<br />
__packed是进行一字节对齐<br />
1.不能对packed的对象进行对齐<br />
2.所有对象的读写访问都进行非对齐访问<br />
3.float及包含float的结构联合及未用__packed的对象将不能字节对齐<br />
4.__packed对局部整形变量无影响<br />
5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定<br />
义为packed。<br />
__packed int* p;  //__packed int 则没有意义<br />
6.对齐或非对齐读写访问带来问题<br />
__packed struct STRUCT_TEST<br />
{<br />
char a;<br />
int b;<br />
char c;<br />
}  ;    //定义如下结构此时b的起始地址一定是不对齐的<br />
//在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]<br />
//将下面变量定义成全局静态不在栈上<br />
static char* p;<br />
static struct STRUCT_TEST a;<br />
void Main()<br />
{<br />
__packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以</p>
<p>p = (char*)&amp;a;<br />
q = (int*)(p+1);</p>
<p>*q = 0&#215;87654321;<br />
/*<br />
得到赋值的汇编指令很清楚<br />
ldr      r5,0&#215;20001590 ; = #0&#215;12345678<br />
[0xe1a00005]   mov      r0,r5<br />
[0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数</p>
<p>[0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问<br />
[0xe1a02420]   mov      r2,r0,lsr #8<br />
[0xe5c12001]   strb     r2,[r1,#1]<br />
[0xe1a02820]   mov      r2,r0,lsr #16<br />
[0xe5c12002]   strb     r2,[r1,#2]<br />
[0xe1a02c20]   mov      r2,r0,lsr #24<br />
[0xe5c12003]   strb     r2,[r1,#3]<br />
[0xe1a0f00e]   mov      pc,r14<br />
*/</p>
<p>/*<br />
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败<br />
[0xe59f2018]   ldr      r2,0&#215;20001594 ; = #0&#215;87654321<br />
[0xe5812000]   str      r2,[r1,#0]<br />
*/<br />
//这样可以很清楚的看到非对齐访问是如何产生错误的<br />
//以及如何消除非对齐访问带来问题<br />
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题<br />
}</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/byte-padding/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>INSERT ON DUPLICATE KEY UPDATE</title>
		<link>http://www.hlouis.com/develop/insert-on-duplicate-key-update/</link>
		<comments>http://www.hlouis.com/develop/insert-on-duplicate-key-update/#comments</comments>
		<pubDate>Wed, 21 Mar 2007 11:38:26 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/insert-on-duplicate-key-update/</guid>
		<description><![CDATA[MySQL INSERT &#8230; ON DUPLICATE KEY UPDATE
INSERT &#8230; ON DUPLICATE KEY UPDATE，当插入的记录会引发主键冲突或者违反唯一约束时，则使用UPDATE更新旧的记录，否则插入新记录。
mysql&#62; desc test;
+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;+&#8212;&#8211;+&#8212;&#8212;&#8212;+&#8212;&#8212;-+
&#124; Field &#124; Type        &#124; Null &#124; Key &#124; Default &#124; Extra &#124;
+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;+&#8212;&#8211;+&#8212;&#8212;&#8212;+&#8212;&#8212;-+
&#124; uid   &#124; int(11)     &#124; NO   &#124; PRI &#124;       [...]]]></description>
			<content:encoded><![CDATA[<p>MySQL INSERT &#8230; ON DUPLICATE KEY UPDATE<br />
INSERT &#8230; ON DUPLICATE KEY UPDATE，当插入的记录会引发主键冲突或者违反唯一约束时，则使用UPDATE更新旧的记录，否则插入新记录。</p>
<p>mysql&gt; desc test;<br />
+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;+&#8212;&#8211;+&#8212;&#8212;&#8212;+&#8212;&#8212;-+<br />
| Field | Type        | Null | Key | Default | Extra |<br />
+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;+&#8212;&#8211;+&#8212;&#8212;&#8212;+&#8212;&#8212;-+<br />
| uid   | int(11)     | NO   | PRI |         |       |<br />
| uname | varchar(20) | YES  |     | NULL    |       |<br />
+&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;+&#8212;&#8211;+&#8212;&#8212;&#8212;+&#8212;&#8212;-+<br />
2 rows in set (0.00 sec)</p>
<p>mysql&gt; select * from test;<br />
+&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
| uid | uname  |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
|   1 | uname1 |<br />
|   2 | uname2 |<br />
|   3 | me     |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8211;+<br />
3 rows in set (0.00 sec)</p>
<p>mysql&gt; INSERT INTO test values ( 3,&#8217;insertName&#8217; )<br />
-&gt; ON DUPLICATE KEY UPDATE uname=&#8217;updateName&#8217;;<br />
Query OK, 2 rows affected (0.03 sec)</p>
<p>mysql&gt; select * from test;+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />
| uid | uname      |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />
|   1 | uname1     |<br />
|   2 | uname2     |<br />
|   3 | updateName |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+<br />
3 rows in set (0.00 sec)</p>
<p>mysql&gt; create index i_test_uname on test(uname);<br />
Query OK, 3 rows affected (0.20 sec)<br />
Records: 3  Duplicates: 0  Warnings: 0</p>
<p>mysql&gt; INSERT INTO test VALUES ( 1 , &#8216;uname2&#8242;)    -&gt; ON DUPLICATE KEY UPDATE uname=&#8217;update2records&#8217;;Query OK, 2 rows affected (0.00 sec)</p>
<p>mysql&gt; select * from test;+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
| uid | uname          |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
|   2 | uname2         |<br />
|   1 | update2records |<br />
|   3 | updateName     |<br />
+&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;&#8212;-+<br />
3 rows in set (0.00 sec)</p>
<p>插入时会与两条记录发生冲突，分别由主键和唯一索引引起。但最终只UPDATE了其中一条。这在手册中也说明了，有多个唯一索引（或者有键也有唯一索引）的情况下，不建议使用该语句。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/insert-on-duplicate-key-update/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to cast member function to pointer</title>
		<link>http://www.hlouis.com/develop/how-to-cast-member-function-to-pointer/</link>
		<comments>http://www.hlouis.com/develop/how-to-cast-member-function-to-pointer/#comments</comments>
		<pubDate>Mon, 12 Mar 2007 10:38:50 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/how-to-cast-member-function-to-pointer/</guid>
		<description><![CDATA[We can cast global function or static function to a function pointer in C/C++
but you can&#8217;t just cast a member function to a pointer, because the member function need context.
But there is a way to make it real:
Define the function pointer:

typedef int (CGameObject::*TClassCmdFunc)(DWORD param1);

this line define a function pointer for the CGameObject class member function

class [...]]]></description>
			<content:encoded><![CDATA[<p>We can cast global function or static function to a function pointer in C/C++</p>
<p>but you can&#8217;t just cast a member function to a pointer, because the member function need context.</p>
<p>But there is a way to make it real:</p>
<p>Define the function pointer:<br />
<code><br />
typedef int (CGameObject::*TClassCmdFunc)(DWORD param1);<br />
</code></p>
<p>this line define a function pointer for the CGameObject class member function<br />
<code><br />
class CGameObject<br />
{<br />
int OneFunction(DWORD param1);<br />
};<br />
</code></p>
<p>when you use it, you need save the pointer to the class object instance and this function pointer:<br />
<code><br />
CGameObject* pObject = &amp;SomeGameObject;<br />
TClassCmdFunc func = (TClassCmdFunc)&amp;CGameObject::OneFunction;<br />
</code></p>
<p>and when U use it, do this:<br />
<code><br />
(pObject-&gt;*func)(param1);<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/how-to-cast-member-function-to-pointer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多服务器的用户身份认证方案</title>
		<link>http://www.hlouis.com/develop/multi-server-auth/</link>
		<comments>http://www.hlouis.com/develop/multi-server-auth/#comments</comments>
		<pubDate>Thu, 01 Feb 2007 11:09:23 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/develop/%e5%a4%9a%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%9a%84%e7%94%a8%e6%88%b7%e8%ba%ab%e4%bb%bd%e8%ae%a4%e8%af%81%e6%96%b9%e6%a1%88/</guid>
		<description><![CDATA[[转自 云风的blog]
当游戏服务器群达到一定规模后，让用户只从一个入口连入会给这个入口带来很大的压力。这样，我们就需要让服务器群中的多台机器都允许用户直接连接。
当服务器开放给用户直接登陆后，必须面临的一个问题就是用户身份认证的问题。
大多数提供网络服务的公司都做了一套统一的用户认证系统，比如微软的 passport ，网易的通行证，等等。为了避免重复验证用户身份而给用户认证系统带来过大的负担，云风在这里给出一个参考解决方案。
登陆服务器可以只有一台，专门用于用户第一次登陆的身份认证。它认证完用户的身份后，则发放一个游戏系统内部用的临时通行证给用户，用户可以拿着这个临时通行证到指定的地点玩游戏。
我们需要讨论的是，以上流程中的安全问题，以及如何加强安全性。
用户认证过程自然不能传送用户密码的全部信息，这跟密码是否加密无关。简单的安全措施是先由登陆服务器发送一个随机串给用户，用户把这个字符串同自己的密码连接起来，并 md5 以后传回服务器做验证。（这里用 md5 指代一种特定的不可逆 hash 算法）
临时通行证的生成可以是这样：登陆服务器从数据库中取得用户所属的服务器 id （通常是他最后一次登陆的位置，或者游戏逻辑认为他所在位置）、用户的游戏 id （往往不同于他的用户名）、一个约定字符串、一个顺序版本号、一些随机字节、把它们连在作一次 des 加密（这里用 des 指代一种特定的可逆密匙加密算法）。每生成一次临时通行证，版本号递增。加密临时通行证的密码由登陆服务器与每个可以接受用户直接连接的服务器约定，永远不传送给用户。
用户拿到这个临时通行证后，可以用之一次性登陆指定服务器。直到从服务器正常登出，若需切入新服务器，可由老服务器生成下一份临时通行证。临时通行证由于被服务器加密，所以对用户来说是一串无意义字符串，所以无法伪造。
临时通行证传送给用户时，可用用户自己的密码之 md5 值（或者连接一个约定串增加安全性）做密匙加密。这样不知道密匙的人截获数据包也不能获得临时身份证。而临时身份证总是一次有效，且只针对特定服务器（服务器会检查临时通行证中包含的版本号、服务器 id ），企图用临时身份证多次登陆也会失败。
]]></description>
			<content:encoded><![CDATA[<p>[转自 <a href="http://blog.codingnow.com/2007/02/user_authenticate.html">云风的blog</a>]</p>
<p>当游戏服务器群达到一定规模后，让用户只从一个入口连入会给这个入口带来很大的压力。这样，我们就需要让服务器群中的多台机器都允许用户直接连接。</p>
<p>当服务器开放给用户直接登陆后，必须面临的一个问题就是用户身份认证的问题。</p>
<p>大多数提供网络服务的公司都做了一套统一的用户认证系统，比如微软的 passport ，网易的通行证，等等。为了避免重复验证用户身份而给用户认证系统带来过大的负担，云风在这里给出一个参考解决方案。</p>
<p>登陆服务器可以只有一台，专门用于用户第一次登陆的身份认证。它认证完用户的身份后，则发放一个游戏系统内部用的临时通行证给用户，用户可以拿着这个临时通行证到指定的地点玩游戏。</p>
<p>我们需要讨论的是，以上流程中的安全问题，以及如何加强安全性。</p>
<p>用户认证过程自然不能传送用户密码的全部信息，这跟密码是否加密无关。简单的安全措施是先由登陆服务器发送一个随机串给用户，用户把这个字符串同自己的密码连接起来，并 md5 以后传回服务器做验证。（这里用 md5 指代一种特定的不可逆 hash 算法）</p>
<p>临时通行证的生成可以是这样：登陆服务器从数据库中取得用户所属的服务器 id （通常是他最后一次登陆的位置，或者游戏逻辑认为他所在位置）、用户的游戏 id （往往不同于他的用户名）、一个约定字符串、一个顺序版本号、一些随机字节、把它们连在作一次 des 加密（这里用 des 指代一种特定的可逆密匙加密算法）。每生成一次临时通行证，版本号递增。加密临时通行证的密码由登陆服务器与每个可以接受用户直接连接的服务器约定，永远不传送给用户。</p>
<p>用户拿到这个临时通行证后，可以用之一次性登陆指定服务器。直到从服务器正常登出，若需切入新服务器，可由老服务器生成下一份临时通行证。临时通行证由于被服务器加密，所以对用户来说是一串无意义字符串，所以无法伪造。</p>
<p>临时通行证传送给用户时，可用用户自己的密码之 md5 值（或者连接一个约定串增加安全性）做密匙加密。这样不知道密匙的人截获数据包也不能获得临时身份证。而临时身份证总是一次有效，且只针对特定服务器（服务器会检查临时通行证中包含的版本号、服务器 id ），企图用临时身份证多次登陆也会失败。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/multi-server-auth/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>VS2005 C++ program publish method</title>
		<link>http://www.hlouis.com/develop/vs2005-c-program-publish-method/</link>
		<comments>http://www.hlouis.com/develop/vs2005-c-program-publish-method/#comments</comments>
		<pubDate>Fri, 29 Dec 2006 07:50:55 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/vs2005-c-program-publish-method/</guid>
		<description><![CDATA[在VS2005下用C++写的程序，在一台未安装VS2005的系统上，
用命令行方式运行，提示：
“系统无法执行指定的程序”
直接双击运行，提示：
“由于应用程序的配置不正确，应用程序未能启动，重新安装应用程序可能会纠正这个问题”
以前用VC6和VS2003的话, 如果缺少库文件，是会提示缺少“**.dll”，但是用VS2005却没有这样的提示。
自己实验了一下，感觉以下几种解决办法是可行的：
方法一：
在类似C:\Program Files\Microsoft Visual Studio 8\VC\redi
st\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT 下找到了下列文件：
msvcm80d.dll
msvcp80d.dll
msvcr80d.dll
Microsoft.VC80.DebugCRT.manifest
把这几个文件拷贝到目标机器上，与运行程序同一文件夹或放到system32下，就可以正确运行了。
其他release版、MFC程序什么的都是拷redist下相应文件夹下的文件就可以了,文件夹后都有标识!
方法二：
修改编译选项，将/MD或/MDd 改为 /MT或/MTd，这样就实现了对VC运行时库的静态链接，在运行时就不再需要VC的dll了。
方法三:
工程－》属性－》配置属性－》常规－》MFC的使用，选择“在静态库中使用mfc”
这样生成的exe文件应该就可以在其他机器上跑了。
方法四:
你的vc8安装盘上找到再分发包vcredist_xxx.exe和你的程序捆绑安装
]]></description>
			<content:encoded><![CDATA[<p>在VS2005下用C++写的程序，在一台未安装VS2005的系统上，<br />
用命令行方式运行，提示：<br />
“系统无法执行指定的程序”<br />
直接双击运行，提示：<br />
“由于应用程序的配置不正确，应用程序未能启动，重新安装应用程序可能会纠正这个问题”</p>
<p>以前用VC6和VS2003的话, 如果缺少库文件，是会提示缺少“**.dll”，但是用VS2005却没有这样的提示。</p>
<p>自己实验了一下，感觉以下几种解决办法是可行的：<br />
方法一：<br />
在类似C:\Program Files\Microsoft Visual Studio 8\VC\redi<br />
st\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT 下找到了下列文件：</p>
<p>msvcm80d.dll<br />
msvcp80d.dll<br />
msvcr80d.dll<br />
Microsoft.VC80.DebugCRT.manifest</p>
<p>把这几个文件拷贝到目标机器上，与运行程序同一文件夹或放到system32下，就可以正确运行了。</p>
<p>其他release版、MFC程序什么的都是拷redist下相应文件夹下的文件就可以了,文件夹后都有标识!</p>
<p>方法二：<br />
修改编译选项，将/MD或/MDd 改为 /MT或/MTd，这样就实现了对VC运行时库的静态链接，在运行时就不再需要VC的dll了。</p>
<p>方法三:</p>
<p>工程－》属性－》配置属性－》常规－》MFC的使用，选择“在静态库中使用mfc”<br />
这样生成的exe文件应该就可以在其他机器上跑了。</p>
<p>方法四:</p>
<p>你的vc8安装盘上找到再分发包vcredist_xxx.exe和你的程序捆绑安装</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/vs2005-c-program-publish-method/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Clock skew detected&#8230;.</title>
		<link>http://www.hlouis.com/develop/clock-skew-detected/</link>
		<comments>http://www.hlouis.com/develop/clock-skew-detected/#comments</comments>
		<pubDate>Wed, 27 Dec 2006 03:43:08 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/develop/clock-skew-detected/</guid>
		<description><![CDATA[When build the libserial on a newly installed Linux server, Make tell me the Clock skew detected. Your build may be incomplete. 
`make&#8217; uses the last modification time on the files to
figure out what to build.  For example, if the modification
time of a.c is later than the modification time of a.out,
then a.c needs to [...]]]></description>
			<content:encoded><![CDATA[<p>When build the libserial on a newly installed Linux server, Make tell me the Clock skew detected. Your build may be incomplete. </p>
<blockquote><p>`make&#8217; uses the last modification time on the files to<br />
figure out what to build.  For example, if the modification<br />
time of a.c is later than the modification time of a.out,<br />
then a.c needs to me recomplied to create a new a.out.</p>
<p>Sometimes the last modified time on the files is wrong:<br />
because it is greater than the time of day clock.  `make&#8217;<br />
then issues the above message.</p>
<p>Given a standard make system, the quickest fix is:</p>
<p>   cd directory<br />
   # Remove output files<br />
   make clean<br />
   # Put timestamps on all files equal to current time<br />
   find . -exec touch {} \;<br />
   # Rebuild all output files<br />
   make</p>
<p>You usually see these sort of problems in programming<br />
enviroments that use NFS to share files but don&#8217;t sync<br />
clocks using NTP.</p>
<p>Similarly, if you wind the clock back you will see these<br />
messages.  For that reason, when you wind back the clock<br />
a moderate amount most UNIXen slow the clock ticks rather<br />
than turn back time.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/clock-skew-detected/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Why link error happen</title>
		<link>http://www.hlouis.com/develop/why-link-error-happen/</link>
		<comments>http://www.hlouis.com/develop/why-link-error-happen/#comments</comments>
		<pubDate>Sun, 10 Dec 2006 14:54:36 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/diary/why-link-error-happen/</guid>
		<description><![CDATA[为什么会出现LNK2005&#8243;符号已定义&#8221;的链接错误?
许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误，而且通常是在使用第三方库时遇到的。对于这个问题，有的朋友可能不知其然，而有的朋友可能知其然却不知其所以然，那么本文就试图为大家彻底解开关于它的种种疑惑。
大家都知道，从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码，然后由汇编器(assembler)翻译成机器指令 (再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中；(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。
编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器，而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢？编译器认为函数与初始化了的全局变量都是强符号，而未初始化的全局变量则成了弱符号。比如有这么个源文件:
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{
return 0;
}
其中main、buf是强符号，p是弱符号，而errorno则非强非弱，因为它只是个外部变量的使用声明。
有了强弱符号的概念，链接器(Unix平台)就会按如下规则(参考[1]，p549~p550)处理与选择被多次定义的全局符号:
规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号)；
规则2: 如果一个符号在某个目标文件中是强符号，在其它文件中都是弱符号，那么选择强符号；
规则3: 如果一个符号在所有目标文件中都是弱符号，那么选择其中任意一个；
虽然上述3条针对的是Unix平台的链接器，但据作者试验，至少VC6.0的linker也遵守这些规则。由此可知多个目标文件不能重复定义同名的函数与初始化了的全局变量，否则必然导致LNK2005和LNK1169两种链接错误。可是，有的时候我们并没有在自己的程序中发现这样的重定义现象，却也遇到了此种链接错误，这又是何解？嗯，问题稍微有点儿复杂，容我慢慢道来。

众所周知，ANSI C/C++ 定义了相当多的标准函数，而它们又分布在许多不同的目标文件中，如果直接以目标文件的形式提供给程序员使用的话，就需要他们确切地知道哪个函数存在于哪个目标文件中，并且在链接时显式地指定目标文件名才能成功地生成可执行文件，显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制，这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名，链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块，并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗？)。
程序库为开发者带来了方便，但同时也是某些混乱的根源。我们来看看链接器(Unix平台)是如何解析(resolve)对程序库的引用的(参考[1]，p556)。
在符号解析(symbol resolution)阶段，链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们，在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合；(2)集合D是所有之前已被加入E的目标文件定义的符号集合；(3)集合U是未解析符号 (unresolved symbols，即那些被E中目标文件引用过但在D中还不存在的符号)的集合。一开始，E、D、U都是空的。
(1): 对命令行中的每一个输入文件f，链接器确定它是目标文件还是库文件，如果它是目标文件，就把f加入到E，并把f中未解析的符号和已定义的符号分别加入到U、D集合中，然后处理下一个输入文件。
(2): 如果f是一个库文件，链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号，那么就把 m加入到E中，并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point)，此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃，链接器继续处理下一输入文件。
(3): 当扫描完所有输入文件时如果U非空或者有同名的符号被多次加入D，链接器报告错误信息并退出。否则，它把E中的所有目标文件合并在一起生成可执行文件。
上述规则针对的是Unix平台链接器，而VC(至少VC6.0)linker则有相当的不同: 它首先依次处理命令行中出现的所有目标文件，然后依照顺序不停地扫描所有的库文件，直至U为空或者某遍(从头到尾依次把所有的库文件扫描完称为一遍)扫描过程中U、D无任何变化时结束扫描，此刻再根据U是否为空以及是否有同名符号重复加入D来决定是出错退出还是生成可执行文件。很明显Unix链接器对输入文件在命令行中出现的顺序十分敏感，而VC的算法则可最大限度地减少文件顺序对链接的影响。作者不清楚Unix下新的开发工具是否已经改进了相应的做法，欢迎有实践经验的朋友补充这方面的信息(补充于2005年10月10日: 经试验，使用gcc 3.2.3的MinGW 3.1.0的链接器表现与参考[1]描述的一致)。
VC带的编译器是cl.exe，它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib)；/MT对应多线程静态版标准库(libcmt.lib)，此时编译器会自动定义_MT宏；/MD对应多线程DLL版 (导入库msvcrt.lib，DLL是msvcrt.dll)，编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个 _DEBUG宏，表示要使用对应标准库的调试版，因此/MLd对应调试版单线程静态标准库(libcd.lib)，/MTd对应调试版多线程静态标准库 (libcmtd.lib)，/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib，DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库，可是当编译器干完了活，轮到链接器开工时它又如何得知一个个目标文件到底在思念谁？为了传递相思，我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和 PE文件格式)存放一些指导链接器如何工作的信息，其中有一项就叫缺省库(default library)，它指定了若干个库文件名，当链接器扫描该目标文件时将按照它们在目标模块中出现的顺序处理这些库名: 如果该库在当前输入文件列表中还不存在，那么便把它加入到输入文件列表末尾，否则略过。说到这里，我们先来做个小实验。写个顶顶简单的程序，然后保存为 main.c :
/* main.c */
int main() { return 0; }
用下面这个命令编译main.c(什么？你从不用命令行来编译程序？这个&#8230;&#8230;) [...]]]></description>
			<content:encoded><![CDATA[<h3>为什么会出现LNK2005&#8243;符号已定义&#8221;的链接错误?</h3>
<p>许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误，而且通常是在使用第三方库时遇到的。对于这个问题，有的朋友可能不知其然，而有的朋友可能知其然却不知其所以然，那么本文就试图为大家彻底解开关于它的种种疑惑。</p>
<p>大家都知道，从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码，然后由汇编器(assembler)翻译成机器指令 (再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中；(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。</p>
<p>编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器，而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢？编译器认为函数与初始化了的全局变量都是强符号，而未初始化的全局变量则成了弱符号。比如有这么个源文件:</p>
<p>extern int errorno;<br />
int buf[2] = {1,2};<br />
int *p;</p>
<p>int main()<br />
{<br />
return 0;<br />
}</p>
<p>其中main、buf是强符号，p是弱符号，而errorno则非强非弱，因为它只是个外部变量的使用声明。</p>
<p>有了强弱符号的概念，链接器(Unix平台)就会按如下规则(参考[1]，p549~p550)处理与选择被多次定义的全局符号:</p>
<p>规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号)；</p>
<p>规则2: 如果一个符号在某个目标文件中是强符号，在其它文件中都是弱符号，那么选择强符号；</p>
<p>规则3: 如果一个符号在所有目标文件中都是弱符号，那么选择其中任意一个；</p>
<p>虽然上述3条针对的是Unix平台的链接器，但据作者试验，至少VC6.0的linker也遵守这些规则。由此可知多个目标文件不能重复定义同名的函数与初始化了的全局变量，否则必然导致LNK2005和LNK1169两种链接错误。可是，有的时候我们并没有在自己的程序中发现这样的重定义现象，却也遇到了此种链接错误，这又是何解？嗯，问题稍微有点儿复杂，容我慢慢道来。</p>
<p><span id="more-44"></span></p>
<p>众所周知，ANSI C/C++ 定义了相当多的标准函数，而它们又分布在许多不同的目标文件中，如果直接以目标文件的形式提供给程序员使用的话，就需要他们确切地知道哪个函数存在于哪个目标文件中，并且在链接时显式地指定目标文件名才能成功地生成可执行文件，显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制，这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名，链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块，并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗？)。</p>
<p>程序库为开发者带来了方便，但同时也是某些混乱的根源。我们来看看链接器(Unix平台)是如何解析(resolve)对程序库的引用的(参考[1]，p556)。</p>
<p>在符号解析(symbol resolution)阶段，链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们，在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合；(2)集合D是所有之前已被加入E的目标文件定义的符号集合；(3)集合U是未解析符号 (unresolved symbols，即那些被E中目标文件引用过但在D中还不存在的符号)的集合。一开始，E、D、U都是空的。</p>
<p>(1): 对命令行中的每一个输入文件f，链接器确定它是目标文件还是库文件，如果它是目标文件，就把f加入到E，并把f中未解析的符号和已定义的符号分别加入到U、D集合中，然后处理下一个输入文件。</p>
<p>(2): 如果f是一个库文件，链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号，那么就把 m加入到E中，并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point)，此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃，链接器继续处理下一输入文件。</p>
<p>(3): 当扫描完所有输入文件时如果U非空或者有同名的符号被多次加入D，链接器报告错误信息并退出。否则，它把E中的所有目标文件合并在一起生成可执行文件。</p>
<p>上述规则针对的是Unix平台链接器，而VC(至少VC6.0)linker则有相当的不同: 它首先依次处理命令行中出现的所有目标文件，然后依照顺序不停地扫描所有的库文件，直至U为空或者某遍(从头到尾依次把所有的库文件扫描完称为一遍)扫描过程中U、D无任何变化时结束扫描，此刻再根据U是否为空以及是否有同名符号重复加入D来决定是出错退出还是生成可执行文件。很明显Unix链接器对输入文件在命令行中出现的顺序十分敏感，而VC的算法则可最大限度地减少文件顺序对链接的影响。作者不清楚Unix下新的开发工具是否已经改进了相应的做法，欢迎有实践经验的朋友补充这方面的信息(补充于2005年10月10日: 经试验，使用gcc 3.2.3的MinGW 3.1.0的链接器表现与参考[1]描述的一致)。</p>
<p>VC带的编译器是cl.exe，它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib)；/MT对应多线程静态版标准库(libcmt.lib)，此时编译器会自动定义_MT宏；/MD对应多线程DLL版 (导入库msvcrt.lib，DLL是msvcrt.dll)，编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个 _DEBUG宏，表示要使用对应标准库的调试版，因此/MLd对应调试版单线程静态标准库(libcd.lib)，/MTd对应调试版多线程静态标准库 (libcmtd.lib)，/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib，DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库，可是当编译器干完了活，轮到链接器开工时它又如何得知一个个目标文件到底在思念谁？为了传递相思，我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和 PE文件格式)存放一些指导链接器如何工作的信息，其中有一项就叫缺省库(default library)，它指定了若干个库文件名，当链接器扫描该目标文件时将按照它们在目标模块中出现的顺序处理这些库名: 如果该库在当前输入文件列表中还不存在，那么便把它加入到输入文件列表末尾，否则略过。说到这里，我们先来做个小实验。写个顶顶简单的程序，然后保存为 main.c :</p>
<p>/* main.c */<br />
int main() { return 0; }</p>
<p>用下面这个命令编译main.c(什么？你从不用命令行来编译程序？这个&#8230;&#8230;) :</p>
<p>cl /c main.c</p>
<p>/c 是告诉cl只编译源文件，不用链接。因为/ML是缺省选项，所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼！当然除非你的环境变量没有设置好，这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。)，当前目录下会出现一个main.obj文件，这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的，文本编辑器，大胆地去做别害怕)，搜索&#8221;defaultlib&#8221;字符串，通常你就会看到这样的东西: &#8220;-defaultlib:LIBC -defaultlib:OLDNAMES&#8221;。啊哈，没错，这就<br />
是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库，一个是单线程静态版标准库libc.lib(这与/ML选项相符)；一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统，基本不用了，为了简化讨论可以忽略它)。另外，如果在源程序中用了</p>
<p>/* xxxx代表实际的库文件名 */<br />
#pragma comment(lib,&#8221;xxxx&#8221;)</p>
<p>编译指示命令(compiler directive)指定要链接的库，那么这个信息也会被保存到目标文件的缺省库信息项中，且位于缺省标准库之前。如果有多个这样的命令，那么对应库名在目标文件中出现的顺序与它们在源程序中出现的顺序完全一致(且都在缺省标准库之前)。</p>
<p>VC的链接器是link.exe，因为main.obj保存了缺省库信息，所以可以用</p>
<p>link main.obj libc.lib</p>
<p>或者</p>
<p>link main.obj</p>
<p>来生成可执行文件main.exe，这两个命令是等价的。但是如果你用</p>
<p>link main.obj libcd.lib</p>
<p>的话，链接器会给出一个警告: &#8220;warning LNK4098: defaultlib &#8220;LIBC&#8221; conflicts with use of other libs; use /NODEFAULTLIB:library&#8221;，因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说，应该保证链接器合并的所有目标文件指定的缺省标准库版本一致，否则编译器一定会给出上面的警告，而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候？呵呵，别着急，下面的一切正是为喜欢追根究底的你准备的。</p>
<p>建一个源文件，就叫mylib.c，内容如下:</p>
<p>/* mylib.c */<br />
#include <stdio.h></stdio.h></p>
<p>void foo(void)<br />
{<br />
printf(&#8220;%s&#8221;,&#8221;I am from mylib!\n&#8221;);<br />
}</p>
<p>用</p>
<p>cl /c /MLd mylib.c</p>
<p>命令编译，注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令，所以我们可以用</p>
<p>lib /OUT:my.lib mylib.obj</p>
<p>将mylib.obj打包成库，输出的库文件名是my.lib。接下来把main.c改成:</p>
<p>/* main.c */<br />
void foo(void);</p>
<p>int main()<br />
{<br />
foo();<br />
return 0;<br />
}</p>
<p>用</p>
<p>cl /c main.c</p>
<p>编译，然后用</p>
<p>link main.obj my.lib</p>
<p>进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误，你仅仅是得到了一条警告信息:&#8221;warning LNK4098: defaultlib &#8220;LIBCD&#8221; conflicts with use of other libs; use /NODEFAULTLIB:library&#8221;。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥(加一个/VERBOSE选项就可以看到详尽的链接过程，但要注意，几乎所有的C编译器都会在符号前加一个下划线后再输出，所以在目标文件和链接输出信息中看到的符号名都比在源程序中见到的多出一个 &#8216;_&#8217;，此点不可不察。)。</p>
<p>一开始E、U、D都是空集。链接器首先扫描main.obj，把它的默认标准库libc.lib加入到输入文件列表末尾，它自己加入E集合，同时未解析的 foo加入U，main加入D。接着扫描my.lib，因为这是个库，所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配，看是否有模块定义了U中的符号。结果mylib.obj确实定义了foo，于是它加入到E，foo从U 转移到D，未解析的printf加入到U，指定的默认标准库libcd.lib也加到输入文件列表末尾(在libc.lib之后)。不断地在my.lib 库的各模块上进行迭代以匹配U中的符号，直到U、D都不再变化。很明显，现在就已经到达了这么一个不动点，所以接着扫描下一个输入文件，就是 libc.lib。链接器发现libc.lib里的printf.obj里定义有printf，于是printf从U移到D，printf.obj加入到 E，它定义的所有符号加入到D，它里头的未解析符号加入到U。如果链接时没有指定/ENTRY(程序入口点选项)，那么链接器默认的入口点就是函数 mainCRTStartup(GUI程序的默认入口点则是WinMainCRTStartup)，它在crt0.obj中被定义，所以crt0.obj 及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中，这些目标模块指定的默认库(只crt0init.obj指定了kernel32.lib)加到输入文件列表末尾，同时更新U和D。不断匹配libc.lib中各模块直至到达不动点，然后处理libcd.lib，但是它里面的所有目标模块都没有定义U中的任何一个符号，所以链接器略过它进入到最后一个输入文件kernel32.lib。事实上，U中已有和将要加入的未解析符号都可以在其中找到定义，那么当处理完kernel32.lib时，U必然为空，于是链接器合并E中的所有模块生成可执行文件。</p>
<p>上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子，接下来你将目睹因为这种不严谨而导致的悲惨失败。</p>
<p>修改mylib.c成这个样子:</p>
<p>#include <crtdbg.h></crtdbg.h></p>
<p>void foo(void)<br />
{<br />
// just a test , don&#8217;t care memory leak<br />
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );<br />
}</p>
<p>其中_malloc_dbg不是ANSI C的标准库函数，它是VC标准库提供的malloc的调试版，与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏，否则预处理器会把它自动转为malloc。继续用</p>
<p>cl /c /MLd mylib.c<br />
lib /OUT:my.lib mylib.obj</p>
<p>编译打包。当再次用</p>
<p>link main.obj my.lib</p>
<p>进行链接时，我们看到了什么？天哪，一堆的LNK2005加上个贵为&#8221;fatal error&#8221;的LNK1169垫底，当然还少不了那个LNK4098。链接器是不是疯了？不，你冤枉可怜的链接器了，我拍胸脯保证它可是一直在尽心尽责地照章办事。</p>
<p>一开始E、U、D为空，链接器扫描main.obj，把libc.lib加到输入文件列表末尾，把main.obj加进E，把foo加进U，把main加进D。接着扫描my.lib，于是mylib.obj加入E，libcd.lib加到输入文件列表末尾，foo从U转移到D，_malloc_dbg加进 U。然后扫描libc.lib，这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在)，所以不会有任何一个模块因为_malloc_dbg而加入E。但因为libc.lib中的crt0.obj定义了默认入口点函数mainCRTStartup，所以crt0.obj及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中，这些目标模块指定的默认库(只 crt0init.obj指定了kernel32.lib)加到输入文件列表末尾，同时更新U和D。不断匹配libc.lib中各模块直至到达不动点后再处理libcd.lib，发现dbgheap.obj定义了_malloc_dbg，于是dbgheap.obj加入到E，它的未解析符号加入U，它定义的所有其它符号加入D，这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的)，而 dbgheap.obj及因它而引入的其它模块又定义了包括malloc在内的许多同名符号，导致了重定义冲突。所以链接器在处理完所有输入文件(是的，即使中途有重定义冲突它也会处理所有的文件以便生成一个完整的冲突列表)后只好报告: 这活儿没法儿干。</p>
<p>现在我们该知道，链接器完全没有责任，责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库 (my.lib)链接起来，引发了大灾难。解决办法很简单，要么用/MLd选项来重编译main.c；要么用/ML选项重编译mylib.c；再或者干脆在链接时用/NODEFAULTLIB:XXX选项忽略默认库XXX，但这种方法非常不保险(想想为什么？)，所以不推荐。</p>
<p>在上述例子中，我们拥有库my.lib的源代码(mylib.c)，所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库，它并没有提供源代码，那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢？其实VC提供的一个小工具便可以完成任务，这就是dumpbin.exe。运行下面这个命令</p>
<p>dumpbin /DIRECTIVES my.lib</p>
<p>然后在输出中找那些&#8221;Linker Directives&#8221;引导的信息，你一定会发现每一处这样的信息都会包含若干个类似&#8221;-defaultlib:XXXX&#8221;这样的字符串，其中XXXX便代表目标模块指定的缺省库名(注意，如果在编译时指定了/Zl选项，那么目标模块中将不会有defaultlib信息)。</p>
<p>知道了第三方库指定的默认标准库，再用合适的选项编译我们的应用程序，就可以避免LNK2005和LNK1169链接错误。喜欢IDE的朋友，你一样可以到 &#8220;Project属性&#8221; -&gt; &#8220;C/C++&#8221; -&gt; &#8220;代码生成(code generation)&#8221; -&gt; &#8220;运行时库(run-time library)&#8221; 项下设置应用程序的默认标准库版本，这与命令行选项的效果是一样的。</p>
<p>参考资料:</p>
<p>[1] 《Computer Systems: A Programmer&#8217;s Perspective》<br />
著:  Randal E. Bryant, David R. O&#8217;Hallaron<br />
电子工业出版社，2004</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/why-link-error-happen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The chaos of  character coding</title>
		<link>http://www.hlouis.com/develop/the-chaos-of-character-coding/</link>
		<comments>http://www.hlouis.com/develop/the-chaos-of-character-coding/#comments</comments>
		<pubDate>Sat, 09 Dec 2006 09:14:40 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[Unicode]]></category>

		<guid isPermaLink="false">http://www.guidemarvin.com/mypool/index.php/develop/the-chaos-of-character-coding/</guid>
		<description><![CDATA[
谈谈Unicode编码，简要解释UCS、UTF、BMP、BOM等名词 （ZT）


这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念，增进知识，类似于打RPG游戏的升级。整理这篇文章的动机是两个问题：

问题一：   
使用Windows记事本的“另存为”，可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件，Windows是怎样识别编码方式的呢？我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几 个字节，分别是FF、FE （Unicode）,FE、FF（Unicode big endian）,EF、BB、BF（UTF-8）。但这些标记是基于什么标准呢？

问题二：   
最近在网上看到一个ConvertUTF.c，实现了UTF-32、UTF-16和UTF-8这三种编 码方式的相互转换。对于Unicode(UCS2)、 GBK、UTF-8这些编码方式，我原来就了解。但这个程序让我有些糊涂，想不起来UTF-16和UCS2有什么关系。  

查了查相关资料，总算将这些问题弄清楚了，顺带也了解了一些Unicode的细节。写成一篇文章，送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂，但要求读者知道什么是字节，什么是十六进制。
0、big endian和little endian
big endian 和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时，究竟是将6C写在前面， 还是将49写在前面？如果将6C写在前面，就是big endian。如果将49写在前面，就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开，由此曾发生过六次叛乱，一个皇帝送了命，另一个丢了王位。
我们一般将endian翻译成“字节序”，将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码，顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码，为了处理汉字，程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符，包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7，低字节从A1-FE，占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号，它分为汉字区和图形符号区。汉字区包括21003个字符。
从ASCII、 GB2312到GBK，这些编码方法是向下兼容的，即同一个字符在这些方案中总是有相同的编码，后面的标准支持更多的字符。在这些编码中，英文和中文可以 统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼，GB2312、GBK都属于双字节字符集 (DBCS)。
2000 年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字，同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。从汉字字 汇上说，GB18030在GB13000.1的20902个汉字的基础上增加了CJK扩展A的6582个汉字（Unicode码0&#215;3400- 0&#215;4db5），一共收录了27484个汉字。
CJK就是中日韩的意思。Unicode为了节省码位，将中日韩三国语言中的文字统一编码。GB13000.1就是ISO/IEC 10646-1的中文版，相当于Unicode 1.1。
GB18030 的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节编码的码位就是收录了CJK扩展A的6582个汉字。 例如： UCS的0&#215;3400在GB18030中的编码应该是8139EF30，UCS的0&#215;3401在GB18030中的编码应该是8139EF31。
微软提供了GB18030的升级包，但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体：新宋体-18030，并不改变内码。Windows 的内码仍然是GBK。
这里还有一些细节：

GB2312的原文还是区位码，从区位码到内码，需要在高字节和低字节上分别加上A0。
对于任何字符编码，编码单元的顺序是由编码方案指定的，与endian无关。例如GBK的编码单元是字节，用两个字 节表示一个汉字。 这两个字节的顺序是固定的，不受CPU字节序的影响。UTF-16的编码单元是word（双字节），word之间的顺序是编码方案指定 的，word内部的字节排列才会受到 endian的影响。后面还会介绍UTF-16。
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有 128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析：在读取DBCS字符流时，只 要遇到高位为1的字节，就可以将下两个字节作为一个双字节编码，而不用管低字节的高位是什么。

2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容（更准确地说，是与ISO-8859-1兼容），与GB码不兼容。例如“汉”字的Unicode编码是6C49，而GB码是BABA。
Unicode 也是一种字符编码方法，不过它是由国际组织设计，可以容纳全世界所有语言文字的编码方案。Unicode的学名是&#8221;Universal Multiple -Octet Coded Character Set&#8221;，简称为UCS。UCS可以看作是&#8221;Unicode Character [...]]]></description>
			<content:encoded><![CDATA[<p><span id="ctl02_ctl00_lblEntry" /></p>
<h4 style="margin-bottom: 0px" class="TextColor1" id="subjcns!AF09A0C8C1C4D6FE!339">谈谈Unicode编码，简要解释UCS、UTF、BMP、BOM等名词 （ZT）</h4>
<div id="msgcns!AF09A0C8C1C4D6FE!339">
<div>
<div><font size="2">这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念，增进知识，类似于打RPG游戏的升级。整理这篇文章的动机是两个问题：</font>
<dl>
<dt><font size="2">问题一： </font>  </dt>
<dd><font size="2">使用Windows记事本的“另存为”，可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件，Windows是怎样识别编码方式的呢？</font><font size="2">我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几 个字节，分别是FF、FE （Unicode）,FE、FF（Unicode big endian）,EF、BB、BF（UTF-8）。但这些标记是基于什么标准呢？</font></p>
</dd>
<dt><font size="2">问题二： </font>  </dt>
<dd><font size="2">最近在网上看到一个ConvertUTF.c，实现了UTF-32、UTF-16和UTF-8这三种编 码方式的相互转换。对于Unicode(UCS2)、 GBK、UTF-8这些编码方式，我原来就了解。但这个程序让我有些糊涂，想不起来UTF-16和UCS2有什么关系。 </font> </dd>
</dl>
<p><font size="2">查了查相关资料，总算将这些问题弄清楚了，顺带也了解了一些Unicode的细节。写成一篇文章，送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂，但要求读者知道什么是字节，什么是十六进制。</font></p>
<h3><font size="2">0、big endian和little endian</font></h3>
<p><font size="2">big endian 和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时，究竟是将6C写在前面， 还是将49写在前面？如果将6C写在前面，就是big endian。如果将49写在前面，就是little endian。</font></p>
<p><font size="2">“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开，由此曾发生过六次叛乱，一个皇帝送了命，另一个丢了王位。</font></p>
<p><font size="2">我们一般将endian翻译成“字节序”，将big endian和little endian称作“大尾”和“小尾”。</font></p>
<h3><font size="2">1、字符编码、内码，顺带介绍汉字编码</font></h3>
<p><font size="2">字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码，为了处理汉字，程序员设计了用于简体中文的GB2312和用于繁体中文的big5。</font></p>
<p><font size="2">GB2312(1980年)一共收录了7445个字符，包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7，低字节从A1-FE，占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。</font></p>
<p><font size="2">GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号，它分为汉字区和图形符号区。汉字区包括21003个字符。</font></p>
<p><font size="2">从ASCII、 GB2312到GBK，这些编码方法是向下兼容的，即同一个字符在这些方案中总是有相同的编码，后面的标准支持更多的字符。在这些编码中，英文和中文可以 统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼，GB2312、GBK都属于双字节字符集 (DBCS)。</font></p>
<p><font size="2">2000 年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字，同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。从汉字字 汇上说，GB18030在GB13000.1的20902个汉字的基础上增加了CJK扩展A的6582个汉字（Unicode码0&#215;3400- 0&#215;4db5），一共收录了27484个汉字。</font></p>
<p><font size="2">CJK就是中日韩的意思。Unicode为了节省码位，将中日韩三国语言中的文字统一编码。GB13000.1就是ISO/IEC 10646-1的中文版，相当于Unicode 1.1。</font></p>
<p><font size="2">GB18030 的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节编码的码位就是收录了CJK扩展A的6582个汉字。 例如： UCS的0&#215;3400在GB18030中的编码应该是8139EF30，UCS的0&#215;3401在GB18030中的编码应该是8139EF31。</font></p>
<p><font size="2">微软提供了GB18030的升级包，但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体：新宋体-18030，并不改变内码。Windows 的内码仍然是GBK。</font></p>
<p><font size="2">这里还有一些细节：</font></p>
<ul>
<li><font size="2">GB2312的原文还是区位码，从区位码到内码，需要在高字节和低字节上分别加上A0。</font></li>
<li><font size="2">对于任何字符编码，编码单元的顺序是由编码方案指定的，与endian无关。例如GBK的编码单元是字节，用两个字 节表示一个汉字。 这两个字节的顺序是固定的，不受CPU字节序的影响。UTF-16的编码单元是word（双字节），word之间的顺序是编码方案指定 的，word内部的字节排列才会受到 endian的影响。后面还会介绍UTF-16。</font></li>
<li><font size="2">GB2312的两个字节的最高位都是1。但符合这个条件的码位只有 128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析：在读取DBCS字符流时，只 要遇到高位为1的字节，就可以将下两个字节作为一个双字节编码，而不用管低字节的高位是什么。</font></li>
</ul>
<h3><font size="2">2、Unicode、UCS和UTF</font></h3>
<p><font size="2">前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容（更准确地说，是与ISO-8859-1兼容），与GB码不兼容。例如“汉”字的Unicode编码是6C49，而GB码是BABA。</font></p>
<p><font size="2">Unicode 也是一种字符编码方法，不过它是由国际组织设计，可以容纳全世界所有语言文字的编码方案。Unicode的学名是&#8221;Universal Multiple -Octet Coded Character Set&#8221;，简称为UCS。UCS可以看作是&#8221;Unicode Character Set&#8221;的缩写。</font></p>
<p><font size="2">根据维基百科全书(<a href="http://zh.wikipedia.org/wiki/%29"><u><font color="#0000ff">http://zh.wikipedia.org/wiki/)</font></u></a>的记载：历史上存在两个试图独立设计Unicode的组织，即国际标准化组织（ISO）和一个软件制造商的协会（unicode.org）。ISO开发了ISO 10646项目，Unicode协会开发了Unicode项目。</font></p>
<p><font size="2">在1991年前后，双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果，并为创立一个单一编码表而协同工作。从Unicode2.0开始，Unicode项目采用了与ISO 10646-1相同的字库和字码。</font></p>
<p><font size="2">目前两个项目仍都存在，并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是ISO 10646-3:2003。</font></p>
<p><font size="2">UCS 只是规定如何编码，并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49，我可以用4个ascii数字来传输、保存这个编码；也可以用 utf-8编码:3个连续的字节E6 B1 89来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。 UTF-8的一个特别的好处是它与ISO-8859-1完全兼容。UTF是“UCS Transformation Format”的缩写。</font></p>
<p><font size="2">IETF 的RFC2781和RFC3629以RFC的一贯风格，清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是 Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。</font></p>
<h4><font size="2">2.1、内码和code page</font></h4>
<p><font size="2">目前Windows的内核已经支持Unicode字符集，这样在内核上可以支持全世界所有的语言文字。但是由于现有的大量程序和文档都采用了某种特定语言的编码，例如GBK，Windows不可能不支持现有的编码，而全部改用Unicode。</font></p>
<p><font size="2">Windows使用代码页(code page)来适应各个国家和地区。code page可以被理解为前面提到的内码。GBK对应的code page是CP936。</font></p>
<p><font size="2">微软也为GB18030定义了code page：CP54936。但是由于GB18030有一部分4字节编码，而Windows的代码页只支持单字节和双字节编码，所以这个code page是无法真正使用的。</font></p>
<h3><font size="2">3、UCS-2、UCS-4、BMP</font></h3>
<p><font size="2">UCS有两种格式：UCS-2和UCS-4。顾名思义，UCS-2就是用两个字节编码，UCS-4就是用4个字节（实际上只用了31位，最高位必须为0）编码。下面让我们做一些简单的数学游戏：</font></p>
<p><font size="2">UCS-2有2^16=65536个码位，UCS-4有2^31=2147483648个码位。</font></p>
<p><font size="2">UCS -4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为 256行 (rows)，每行包含256个cells。当然同一行的cells只是最后一个字节不同，其余都相同。</font></p>
<p><font size="2">group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中，高两个字节为0的码位被称作BMP。</font></p>
<p><font size="2">将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节，就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。</font></p>
<h3><font size="2">4、UTF编码</font></h3>
<p><font size="2">UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下：</font></p>
<table width="75%" border="1">
<tr>
<td><font size="2">UCS-2编码(16进制)</font></td>
<td><font size="2">UTF-8 字节流(二进制)</font></td>
</tr>
<tr>
<td><font size="2">0000 &#8211; 007F</font></td>
<td><font size="2">0xxxxxxx</font></td>
</tr>
<tr>
<td><font size="2">0080 &#8211; 07FF</font></td>
<td><font size="2">110xxxxx 10xxxxxx</font></td>
</tr>
<tr>
<td><font size="2">0800 &#8211; FFFF</font></td>
<td><font size="2">1110xxxx 10xxxxxx 10xxxxxx</font></td>
</tr>
</table>
<p><font size="2">例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间，所以肯定要用3字节模板了：</font> <font size="2" color="#0000ff">1110</font> <font size="2">xxxx </font> <font size="2" color="#0000ff">10</font> <font size="2">xxxxxx </font> <font size="2" color="#0000ff">10</font> <font size="2">xxxxxx。将6C49写成二进制是：0110 110001 001001， 用这个比特流依次代替模板中的x，得到：</font> <font size="2" color="#0000ff">1110</font> <font size="2">0110 </font> <font size="2" color="#0000ff">10</font> <font size="2">110001 </font> <font size="2" color="#0000ff">10</font> <font size="2">001001，即E6 B1 89。</font></p>
<p><font size="2">读者可以用记事本测试一下我们的编码是否正确。需要注意，UltraEdit在打开utf-8编码的文本文件时会自动转换为UTF-16，可能产生混淆。你可以在设置中关掉这个选项。更好的工具是Hex Workshop。</font></p>
<p><font size="2">UTF -16以16位为单元对UCS进行编码。对于小于0&#215;10000的UCS码，UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0&#215;10000的UCS码，定义了一个算法。不过由于实际使用的UCS2，或者UCS4的BMP必然小于0&#215;10000，所以就目前而言，可以认为UTF -16和UCS-2基本相同。但UCS-2只是一个编码方案，UTF-16却要用于实际的传输，所以就不得不考虑字节序的问题。</font></p>
<h3><font size="2">5、UTF的字节序和BOM</font></h3>
<p><font size="2">UTF -8以字节为编码单元，没有字节序的问题。UTF-16以两个字节为编码单元，在解释一个UTF-16文本前，首先要弄清楚每个编码单元的字节序。例如 “奎”的Unicode编码是594E，“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”，那么这是“奎”还是 “乙”？</font></p>
<p><font size="2">Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表，而是Byte Order Mark。BOM是一个有点小聪明的想法：</font></p>
<p><font size="2">在UCS 编码中有一个叫做&#8221;ZERO WIDTH NO-BREAK SPACE&#8221;的字符，它的编码是FEFF。而FFFE在UCS中是不存在的字符，所以不应该 出现在实际传输中。UCS规范建议我们在传输字节流前，先传输字符&#8221;ZERO WIDTH NO-BREAK SPACE&#8221;。</font></p>
<p><font size="2">这样如果接收者收到FEFF，就表明这个字节流是Big-Endian的；如果收到FFFE，就表明这个字节流是Little-Endian的。因此字符&#8221;ZERO WIDTH NO-BREAK SPACE&#8221;又被称作BOM。</font></p>
<p><font size="2">UTF -8不需要BOM来表明字节顺序，但可以用BOM来表明编码方式。字符&#8221;ZERO WIDTH NO-BREAK SPACE&#8221;的UTF-8编码是 EF BB BF（读者可以用我们前面介绍的编码方法验证一下）。所以如果接收者收到以EF BB BF开头的字节流，就知道这是UTF-8编码了。</font></p>
<p><font size="2">Windows就是使用BOM来标记文本文件的编码方式的。</font></p>
<h3><font size="2">6、进一步的参考资料</font></h3>
<p><font size="2">本文主要参考的资料是 &#8220;Short overview of ISO-IEC 10646 and Unicode&#8221; (<a href="http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html%29"><u><font color="#0000ff">http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)</font></u></a>。</font></p>
<p><font size="2">我还找了两篇看上去不错的资料，不过因为我开始的疑问都找到了答案，所以就没有看：</font></p>
<ol>
<li><font size="2">&#8220;Understanding Unicode A general introduction to the Unicode Standard&#8221; (<a href="http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter04a%29%C2%A0"><u><font color="#0000ff">http://scripts.sil.org/cms/scrip &#8230; S-Chapter04a) </font></u></a></font></li>
<li><font size="2">&#8220;Character set encoding basics Understanding character set encodings and legacy encodings&#8221; (<a href="http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter03%29%C2%A0"><u><font color="#0000ff">http://scripts.sil.org/cms/scrip &#8230; WS-Chapter03) </font></u></a></font></li>
</ol>
<p><font size="2">我写过UTF-8、UCS-2、GBK相互转换的软件包，包括使用Windows API和不使用Windows API的版本。以后有时间的话，我会整理一下放到我的个人主页上(<a href="http://fmddlmyy.home4u.china.com%29/"><u><font color="#0000ff">http://fmddlmyy.home4u.china.com)</font></u></a>。</font></p>
<p><font size="2">我是想清楚所有问题后才开始写这篇文章的，原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间，竟然从下午1:30写到9:00。希望有读者能从中受益。</font></p>
<h3><font size="2">附录1 再说说区位码、GB2312、内码和代码页</font></h3>
<p><font size="2">有的朋友对文章中这句话还有疑问：<br />
“GB2312的原文还是区位码，从区位码到内码，需要在高字节和低字节上分别加上A0。”</font></p>
<p><font size="2">我再详细解释一下：</font></p>
<p><font size="2">“GB2312 的原文”是指国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集 基本集 GB 2312-80》。这个标准用两个数来编码汉 字和中文符号。第一个数称为“区”，第二个数称为“位”。所以也称为区位码。1-9区是中文符号，16-55区是一级汉字，56-87区是二级汉字。现在 Windows也还有区位输入法，例如输入1601得到“啊”。（这个区位输入法可以自动识别16进制的GB2312和10进制的区位码，也就是说输入 B0A1同样会得到“啊”。）</font></p>
<p><font size="2">内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内部支持Unicode，然后用代码页适应各种语言，“内码”的概念就比较模糊了。微软一般将缺省代码页指定的编码说成是内码。</font></p>
<p><font size="2">内码这个词汇，并没有什么官方的定义，代码页也只是微软这个公司的叫法。作为程序员，我们只要知道它们是什么东西，没有必要过多地考证这些名词。</font></p>
<p><font size="2">所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936，BIG5的code page是CP950，GB2312的code page是CP20936。</font></p>
<p><font size="2">Windows中有缺省代码页的概念，即缺省用什么编码来解释字符。例如Windows的记事本打开了一个文本文件，里面的内容是字节流：BA、BA、D7、D6。Windows应该去怎么解释它呢？</font></p>
<p><font size="2">是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释，还是按照ISO8859-1去解释？如 果按GBK去解释，就会得到“汉字”两个字。按照其它编码解释，可能找不到对应的字符，也可能找到错误的字符。所谓“错误”是指与文本作者的本意不符，这 时就产生了乱码。</font></p>
<p><font size="2">答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI，其实就是按照缺省代码页的编码方法保存。</font></p>
<p><font size="2">Windows的内码是Unicode，它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编码，用户又安装了对应的代码页，Windows就能正确显示，例如在HTML文件中就可以指定charset。</font></p>
<p><font size="2">有的HTML文件作者，特别是英文作者，认为世界上所有人都使用英文，在文件中不指定charset。如果他使用了 0&#215;80-0xff之间的字符，中文 Windows又按照缺省的GBK去解释，就会出现乱码。这时只要在这个html文件中加上指定charset的语句，例如：<br />
<meta content="text/html; charset=ISO8859-1" http-equiv="Content-Type" /><br />
如果原作者使用的代码页和ISO8859-1兼容，就不会出现乱码了。</font></p>
<p><font size="2">再说区位码，啊的区位码是1601，写成16进制是0&#215;10,0&#215;01。这和计算机广泛使用的ASCII编码冲突。 为了兼容00-7f的ASCII编码，我们在区位码的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。我们将加过两个A0的编码也称为 GB2312编码，虽然GB2312的原文根本没提到这一点。<br />
</font></div>
<div>
<div><font size="2"><strong>UTF-16</strong>是Unicode的其中一个使用方式。 UTF是 <em>Unicode Translation Format</em>，即把Unicode转做某种格式的意思。 </font><font size="2">它定义于ISO/IEC 10646-1的附录Q，而RFC2781也定义了相似的做法。 </font></p>
<p><font size="2">在Unicode基本多文种平面定义的字符（无论是拉丁字母、汉字或其他文字或符号），一律使用2字节储存。而在辅助平面定义的字符，会以<em>代理对</em>（surrogate pair）的形式，以两个2字节的值来储存。 </font></p>
<p><font size="2">UTF-16比起UTF-8，好处在于大部分字符都以固定长度的字节 (2字节) 储存，但UTF-16却无法兼容于ASCII编码。 </font></p>
<h2><font size="2">UTF-16的编码模式</font></h2>
<p><font size="2">UTF-16的大尾序和小尾序储存形式都在用。一般来说，以Macintosh制作或储存的文字使用大尾序格式，以Microsoft或Linux制作或储存的文字使用小尾序格式。 </font></p>
<p><font size="2">为了弄清楚UTF-16文件的大小尾序，在UTF-16文件的开首，都会放置一个U+FEFF字符作为Byte Order Mark (UTF-16LE 以 FF FE 代表，UTF-16BE 以 FE FF 代表)，以显示这个文字档案是以UTF-16编码。 </font></p>
<p><font size="2">以下的例子有四个字符：“朱”、半角逗号、“聿”、“</p>
]]></content:encoded>
			<wfw:commentRss>http://www.hlouis.com/develop/the-chaos-of-character-coding/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
