<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>局部变量的作用域</title><link>http://blog.vckbase.com/localvar/</link><description /><managingEditor>局部变量</managingEditor><dc:language>zh-CHS</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>局部变量</dc:creator><title>多线程和函数里的静态变量</title><link>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html</link><pubDate>Thu, 29 May 2008 01:33:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/33945.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/05/29/33945.html#Feedback</comments><slash:comments>16</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/33945.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/33945.html</trackback:ping><description>&lt;DIV&gt;试试下面这段代码的输出是什么?&lt;/DIV&gt;
&lt;TABLE style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="75%" bgColor=#f1f1f1 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P style="MARGIN: 5px; LINE-HEIGHT: 150%"&gt;&lt;CODE&gt;&lt;SPAN style="COLOR: #000000"&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;stdio&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;process&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;#&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;include&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;windows&lt;SPAN style="COLOR: #0000cc"&gt;.&lt;/SPAN&gt;h&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;class&lt;/SPAN&gt; foo&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;public&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foo&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"before sleep\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;Sleep&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; 1000 &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"after sleep\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;void&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;test&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;printf&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"in test\n"&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;foo&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; bar&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;static&lt;/SPAN&gt; foo a&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;&amp;amp;&lt;/SPAN&gt;a&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;unsigned&lt;/SPAN&gt; __stdcall thread&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;void&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foo&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; p &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; bar&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p&lt;SPAN style="COLOR: #0000cc"&gt;-&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;test&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; _cdecl main&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; argc&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;char&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;*&lt;/SPAN&gt; argv &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;for&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; i &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; i &lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt; 10&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;+&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;+&lt;/SPAN&gt;i &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;{&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;uintptr_t&lt;/SPAN&gt; t &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; _beginthreadex&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; thread&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;,&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;NULL&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CloseHandle&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; &lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;HANDLE&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;t &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #ff0000"&gt;Sleep&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt; 5000 &lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;return&lt;/SPAN&gt; 0&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;}&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;BR&gt;不知道C/C++标准有什么规定没有, 但粗看起来好像是编译器的问题呀。我用的是vc8，谁帮忙测测别的编译器。&lt;BR&gt;根据星星的建议，把输出贴出来，如下：&lt;BR&gt;before sleep&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;in test&lt;BR&gt;after sleep&lt;BR&gt;in test&lt;BR&gt;这里的问题是至少有10个中的9个线程没有等对象初始化完成，就已经调用对象的方法了，这肯定是不对的。我大概看了一下反汇编的结果，实际上还可能出现构造函数被调用多次的情况。&lt;BR&gt;要解决这个问题，在编译器的层次上要容易一点。如果是在用户程序的层次上，则麻烦的多，因为这类方法都会涉及到另一个静态变量的初始化。&lt;img src ="http://blog.vckbase.com/localvar/aggbug/33945.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>使用SVN实现版本号自增</title><link>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html</link><pubDate>Tue, 20 May 2008 01:12:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/33718.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/05/20/33718.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/33718.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/33718.html</trackback:ping><description>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在《介绍一下SVN》一文中，我提到了自动递增版本号的功能，现在就来具体说明一下实现方法。虽然标题中说的是&amp;#8220;使用SVN&amp;#8221;，但我们实际用的是SVN的客户端工具TortoiseSVN中的SubWCRev程序。另外文中的例子也使用了Visual Studio的SVN插件VisualSVN，它并非必须，用了方便一些，不用也行。我平时主要使用C/C++语言，但考虑C#有更大的用户群，我的示例项目也采用了C#。
&lt;H3&gt;&lt;/H3&gt;
&lt;H4&gt;1.&amp;nbsp;生成一个名为autover的项目&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;注意项目的Properties文件夹下有一个名为AssemblyInfo.cs的文件，autover程序的版本号就写在它里面。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image001.jpg"&gt; 
&lt;H4&gt;2.&amp;nbsp;创建模板文件&lt;/H4&gt;
&lt;P&gt;在windows的资源管理器中进入Properties文件夹，把AssemblyInfo.cs文件复制一份，命名为AssemblyInfo.template.cs，并把它加入到项目中来。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image002.jpg"&gt; 
&lt;H4&gt;3.&amp;nbsp;修改AssemblyInfo.template.cs的属性&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AssemblyInfo.template.cs文件是用来自动生成版本号的模板文件，它不应该被编译，所以我们要把它的Build Action改成None，如下图所示：&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image003.jpg"&gt; 
&lt;H4&gt;4.&amp;nbsp;修改AssemblyInfo.template.cs的内容&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在这个文件中，你能找到如下两行代码： &lt;BR&gt;&lt;FONT color=#0000ff&gt;[assembly: AssemblyVersion( "1.0.0.0" )] &lt;BR&gt;[assembly: AssemblyFileVersion( "1.0.0.0" )] &lt;BR&gt;&lt;/FONT&gt;其中的&amp;#8220;1.0.0.0&amp;#8221;就是程序的版本号，它使用的是&amp;#8220;主版本号.次版本号.内部版本号.修订号&amp;#8221;的形式。前三个改成你自己需要的数字，最后一个改成&amp;#8220;$WCREV$&amp;#8221;，改完之后应该是类似下面的样子： &lt;BR&gt;&lt;FONT color=#0000ff&gt;[assembly: AssemblyVersion( "1.0.0.$WCREV$" )] &lt;BR&gt;[assembly: AssemblyFileVersion( "1.0.0.$WCREV$" )]&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在这个文件的最后，你还应该加上下面两段代码，它们可以检测出有本地修改（修改了但没有提交）的代码和有混合版本的代码。 &lt;BR&gt;&lt;FONT color=#0000ff&gt;#if $WCMIXED?true:false$ &lt;BR&gt;#if DEBUG &lt;BR&gt;#warning mixed update revisions founded &lt;BR&gt;#else &lt;BR&gt;#error mixed update revisions founded &lt;BR&gt;#endif &lt;BR&gt;#endif &lt;BR&gt;&lt;BR&gt;#if $WCMODS?true:false$ &lt;BR&gt;#if DEBUG &lt;BR&gt;#warning local modification founded &lt;BR&gt;#else &lt;BR&gt;#error local modification founded &lt;BR&gt;#endif &lt;BR&gt;#endif&lt;/FONT&gt;&lt;/P&gt;
&lt;H4&gt;5.&amp;nbsp;修改项目属性&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在项目属性的Build Event页的Pre-build event command line中输入： &lt;BR&gt;&lt;FONT color=#0000ff&gt;"%ProgramFiles%\TortoiseSVN\bin\SubWCRev.exe" $(SolutionDir) $(ProjectDir)Properties\AssemblyInfo.template.cs $(ProjectDir)Properties\AssemblyInfo.cs -f &lt;BR&gt;&lt;/FONT&gt;注意，这里我们必须保证TortoiseSVN安装到了默认路径上。在多人参加的项目中这应该是强制性的要求，否则，大家安装的路径都不一样，甲机器上能用的配置，到了乙机器上可能就不行了。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;然后，你可能还需要将Publish页中的Automatically increment revision with each publish选项关掉（我不确定这步是否必须）。C#可以自己递增版本号，但它生成的版本号和代码库中的代码没有对应关系，我个人觉得意义不大。并且它还可能会把我们的版本自增机制搞乱。所以应该关掉。&lt;/P&gt;
&lt;H4&gt;6.&amp;nbsp;把项目加入版本库&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;使用VisualSVN的Add solution to Subversion命令把项目加入SVN，但不要提交。&lt;/P&gt;
&lt;H4&gt;7.&amp;nbsp;从SVN中排除AssemblyInfo.cs文件&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;每次编译时，这个文件都会基于AssemblyInfo.template.cs重新生成，所以没必要加入版本库。这步做完之后就可以提交整个项目了。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image004.jpg"&gt; 
&lt;H4&gt;8.&amp;nbsp;编译&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;编译完成后，看一下生成的autover.exe文件的版本信息，本例中是1.0.0.1。随便改点什么，提交，重新编译，你会发现它自动变成了1.0.0.2，也就是程序的修订号总是与生成它的代码的修订号一致。这样，当程序出问题后，我们通过这个数字就能轻松得到生成它的那一版代码了。&lt;/P&gt;&lt;IMG src="/images/vckbase_com/localvar/1234/o_image005.jpg"&gt; 
&lt;H4&gt;9.&amp;nbsp;其它问题&lt;/H4&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第8步中大家做完修改后再编译时可能会看到警告或错误信息，说代码有本地修改或混合版本。这就是第4步中，在AssemblyInfo.template.cs文件最后加的两段代码的作用，它们检测代码是否都已经提交了并且版本是否一致，一旦发现问题就会在调试版中生成警告信息，在发布版中生成错误信息。使用这种方法，我们可以基本消除发布的程序的版本和代码的版本出现不一致的可能性。去掉这两个错误或警告的方法也很简单，把代码整体提交或更新一下就行了。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/33718.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>设计的载体是什么？</title><link>http://blog.vckbase.com/localvar/archive/2008/03/12/32912.html</link><pubDate>Wed, 12 Mar 2008 05:36:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/03/12/32912.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32912.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/03/12/32912.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32912.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32912.html</trackback:ping><description>&lt;P&gt;也是一篇准备发在公司内刊上的文章。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 从参加工作开始，我就一直困惑于如何写设计文档。公司的各种规定和规范看起来很美，但真的执行起来却总让我头痛不已；同时，我也觉得自己喜欢的方式有些自由散漫，满足不了商业化开发的要求。直到前不久，偶然在网上看到了一篇题为《源代码就是设计》的文章后，我终于感觉自己把这个问题想得清楚些了，所以写了本文，与大家交流。这也许是篇偏激的文章，但我相信它提到的很多问题是我们难以回避的。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 公司一直把文档作为设计的主要部分，并且，我认为，公司对这份文档的要求至少包含以下几点：&lt;BR&gt;1）&amp;nbsp;能够指导程序员编码。最好是设计师可以完全脱离编码，完成设计后能立即投入下一个项目。&lt;BR&gt;2）&amp;nbsp;提供评审和QA监督的依据，以便尽早发现不合格项，尽早纠正。&lt;BR&gt;3）&amp;nbsp;作为项目维护的依据。&lt;BR&gt;但仔细想想会发现，文档并非做到这几点的必要条件。换句话说，有了文档不见得能做到，没有文档也不见得做不到。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 首先，只要设计没有细化到本身就是代码的程度，程序员就不可能仅参照设计编码。这是因为，文档&amp;#8212;&amp;#8212;即使是写作过程中使用了很多软件设计工具的文档&amp;#8212;&amp;#8212;也是一种更接近自然语言的东西，而自然语言有一个天生的弊端&amp;#8212;&amp;#8212;二意性。所以，不论它写的有多好，程序员理解起来也不会与设计师所想的完全一致。而让设计师完全脱离编码也是一个不太现实的想法，一个设计师或许能在一个项目中这样做，但如果每个项目是都是如此的话，他日后肯定会眼高手低，那时他的设计就根本没法用了。我们也许听到过一些新闻，说国外某项目的主设计师根本没写过代码。我不否认这些新闻，但我认为我们绝不能照搬这种模式，因为那些设计师往往是某方面的世界级权威，而人比人是要死的。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其次，评审和QA方面的目标也基本没有可能达到。因为大多数情况下这两个过程是&amp;#8220;外行监督内行&amp;#8221;。我这里并不是说参加评审的同事和项目组的QA水平不足，只是说相对于项目组成员，他们对项目的投入要少的多，对项目细节的了解也少的多。这种情况下还想让他们提出评审问题或监督项目执行，要求未免高了一些。以XXXXX产品为例，评审阶段发现的问题数一般不多，到了Y项目更是可怜，只有组织级要求的几分之一了。而如果再仔细观察一下这些问题，还会发现其中绝大部分无关紧要，真正价值较大的往往只有一两个。至于QA，由于其监督的主要是过程，所以更容易糊弄，并且极有可能是项目组的故意糊弄（评审上的一般是无意的）。我相信这种糊弄在各个公司都不罕见，前一段时间来培训单元测试的讲师就讲到过他们从前（应该是在华为）在项目中糊弄QA的事例。作为反例，公司的统计数据已经表明，代码走查这个内行监督外行的过程发现问题的效率是最高的。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;第三，已有的经验也说明设计文档在项目维护过程中没多大用。从公司内部看，到目前为止我还不知道哪个项目是主要靠设计文档维护的。项目出问题后，我们首先想到的一般是找参与过项目的人，然后才是文档和其它的东西。我想这个过程应该能说明：人，而不是文档，才是项目维护中最重要的因素。当然，我们也可以说这是因为文档写的不够完美，不过我希望你读完本文后能同意&amp;#8220;完美的文档是不可能的&amp;#8221;这个观点。再者，从我接触过的开源项目看，项目的维护也不需要文档，开源项目一般没有太好的设计文档，但人们还是能仅仅通过源码和注释等就参与进去。另外微软的文档应该是非常好了，可开发时我还是要经常去看看mfc、atl的源代码，因为那才是最准确的第一手资料，msdn上描述的不太清楚的东西，看看源码就一目了然了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其实，我觉得，把文档看成设计不光满足不了公司的要求，还会带来其他的一些弊端，比如工作效率和一致性等。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;作为开发人员，我想没有几个人喜欢写文档，我们常说&amp;#8220;兴趣就是动力&amp;#8221;，那没了兴趣肯定也就没动力了，所以写文档的效率也就可想而知了。以我自己做例子，我认为自己写文档的效率比写代码低的多。即使抛开主观因素，由于中英文输入效率的差别和画图与打字的效率差别，写文档的也要写代码慢不少。所以，一份&amp;#8220;完美的设计文档&amp;#8221;可能会大幅增加项目成本，以至于不能公司不会接受。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 再进一步，就算能写出来一份&amp;#8220;完美的设计文档&amp;#8221;，我们也不可能一劳永逸，因为设计会变更。大家往往非常关注需求变更，但设计变更发生的更频繁，道理很简单：需求变更会导致设计变更，需求不变更设计也可能变更。设计发生变更的时候，代码或许已经完成了，加上工期的要求等，开发人员很有可能会先改代码后改文档，这时问题就出现了：开发人员会&amp;#8220;忘了&amp;#8221;改文档。或者即使他没忘，由于这份文档的初稿很&amp;#8220;完美&amp;#8221;，他要修改的地方会很多，工作量会很大，甚至要花数倍于他改代码所用的时间才行，再考虑上人的惰性、其他工作的压力等因素，他敷衍了事应该是再正常不过的了。而我们前面已经分析过，这种敷衍是很难被发现的。反正不管是因为什么原因，经过变更后，我们的&amp;#8220;完美&amp;#8221;文档就和代码对不上了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; XXXXX是一个以数据处理为主要功能的项目，所以我在刚开始时考虑了很长时间&amp;#8220;在不能保证数据的正确性时应该怎么办&amp;#8221;这个问题，最后的决定是把这些数据扔掉，因为我认为&amp;#8220;错误的数据不如没有数据&amp;#8221;。把这个原则应用到项目上来应该就是：错误的文档不如没有文档，即使这份文档看起来很&amp;#8220;完美&amp;#8221;。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 回到《源代码就是设计》这篇文章上来。实际上，仅仅是它的标题就让我眼前一亮了。从上学时的《软件工程》教科书，到工作后的实际项目，先入为主的&amp;#8220;设计是文档&amp;#8221;让我一直没有仔细想过设计应该是什么。这篇文章给了我一个思考的机会，并且经过仔细比较后我发现：代码是比文档更好的设计载体，甚至是天生的设计载体。&lt;BR&gt;&amp;nbsp;我们经常把做软件和盖房子做对比，并把程序员比作建筑工人，这也带来了所谓&amp;#8220;软件蓝领&amp;#8221;的称呼。但我要说这个对比把程序员和建筑工人放在一块是不恰当的，最简单的理由是建筑工人把砖块磊整齐了非常重要，程序员把代码排整齐了则没那么重要（虽然也很有用）。我认为：程序员应该对应建筑设计师，编码应该对应画图纸。你也许会问：这个对比中，磊砖块对应什么？别着急，软件开发中还有一个大家已经见的太多以至于被忽视了的过程&amp;#8212;&amp;#8212;编译，它对应着磊砖块，也就是把设计变成现实的这个过程，只不过由于软件开发的自动化程度太高，它完全被计算机完成了而已。如果我们把盖房子换成加工机器零件，这个对比的正确性就更明显了，现在自控机床已经能自动加工零件了，而以前，这个工作是要很多人工的。或许未来会有一种机器能根据图纸自动盖房子。我们发现了编译这个之前一直被忽视了的过程之后，应该会注意到，在把文档看成设计的那个对比中，盖房子没有与编译对应的过程，这也说明它是不恰当的。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 把代码看成设计的一个显而易见的好处是消除了设计和实现的不一致性，编译代码速度很快，时间成本极低并且基本不需要人工，所以保证这二者的一致是非常容易的。另外它也提高了工作效率，因为主观方面开发人员更喜欢写代码，客观方面英文输入速度比中文快得多。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 虽然有不少好处，但从文档到代码的这个转变有点太大，恐怕大家会非常怀疑它是否真的可行。下面我就来分析一下大家可能的疑问。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第一个问题是设计师和程序员如何协作，也就是说设计师怎么让程序员按照自己的想法去做事情。我们先来看设计到底是怎么进行的，乍一看大家可能认为设计是&amp;#8220;想&amp;#8221;出来的，但实际上它更多的是&amp;#8220;试&amp;#8221;出来的。任何一个项目都会有一些设计师之前没遇到过的问题，区别只是多少而已。对这些问题，设计师可能会在脑子里想一些方法，然后他就要去试验这些方法是否真的可行，而试验成功之时，也是针对这些问题的设计完成之时，并且，往往也是相关代码写完之时。基于这一点，我认为设计师在一个项目中的工作是完成系统框架和技术难点的开发，程序员则去做具体细节的开发。细节中的一些问题可能是程序员没有遇到过的，解决这些问题的过程，不管是完全靠自己还是求助于设计师，对程序员来说也是一个做&amp;#8220;设计&amp;#8221;的过程。所以，这种模式下，是不必严格区分设计师和程序员的，大家都是设计师也都是程序员，而这也正好符合&amp;#8220;源代码是设计&amp;#8221;这一观点。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第二个问题是如何审查监督。我的看法是把这项工作交给项目经理或主设计师，而不做严格的外部审查和监督。自己监督自己也许听起来不那么可行，但我相信大多数人，尤其是项目经理和主设计师，对项目是负责的，所以这样做效果不一定就不好。用人不疑，疑人不用，这句老话用在这里应该很合适。而且，我们前面已经分析过，如果项目组成员成心欺骗，外部监督机制也不见得有多大效果。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第三个问题是项目的风险会不会增大。设计做的不好一直被认为是项目拖期的主要原因之一，现在这个（按原来的观点）看起来根本没有设计的模式会不会加大这个风险呢？我觉得不会，因为它把编码提前了，而编码阶段是最容易发现问题的阶段，所以设计上的问题会被更早的发现，更早的修正，从而降低项目风险。另外，代码重构（在这个模式中相当于设计变更）的代价可能也没有想象的那么大，我自己经常重构大量代码，我觉得花的时间并不多。最后说句实话，XXXXX相关的项目一直都是先编码后文档，但它并没有因此发生过拖期，而且从公司的统计数字看，它的生产率也一直是高于组织级标准。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第四个问题是项目如何维护。前面我已经论证了文档不可能作为项目维护的依据，或者至少是主要依据，所以没有文档也不会让这个情况再变坏多少。另外，不做开发的人可能会觉得文档更易读，但开发人员就不是这样了，他们受过专业训练，对代码的条件反射更强烈，就像音乐家看到123会首先想到哆来咪一样。因此，项目维护是可以基于代码进行的，而且，稍后我会说明这种新的开发模式也并非没有任何文档。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前面的文字应该已经基本证明了&amp;#8220;源代码就是设计&amp;#8221;的可行性，但一个项目如果只有代码肯定也不行，因为代码的组织很松散，开发人员很难通过它们形成对系统的总体认识。所以，一些文档是必须的。下面我就对&amp;#8220;应该有哪些文档&amp;#8221;和&amp;#8220;这些文档应该怎么写&amp;#8221;发表一下看法。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第一份应该有的文档是需求，虽然它是需求阶段的输出，但我也要在这里强调它。我认为需求是一个项目中最重要的文档，它规定了系统开发出来后应该是什么样的，必须全面而详细。有经验的开发人员往往看到需求就知道&amp;#8220;应该如何实现这个系统&amp;#8221;或者&amp;#8220;这个系统应该是怎么实现的&amp;#8221;了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第二份文档是系统的总体结构图和一些简要说明，它应该在系统框架开发完成后输出，以便指导项目的后续开发。这份文档可能只要很少的几页就够了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第三份文档是通讯协议格式和文件格式等，这些内容是结构性的，而不是逻辑性的，所以文档能更清晰的把它们描述出来。数据库格式也可以写，但考虑到可以在大多数DBMS中直接看到数据库结构，它的必要性并不大。这些文档可以在相关部分开发完毕后输出，也可以等整个项目开发完了再输出。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 第四份文档一些描述系统关键点实现方法的论文。由于针对单个问题，所以它们的主题很明确，不大可能因为太松散而失去价值。这些论文可以等项目结束后再写。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 总之，我认为设计相关的文档应该等代码完成之后再写，这样最大程度的避免了文档和代码的不一致。这些文档的篇幅也不宜过大，把一些框架性的东西说清即可，描述具体细节是代码的事。另外我觉得公司也可以考虑为项目组或个人建立内部BLOG，并鼓励大家发表相关文章，以促进交流。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32912.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>转载：extern "C"</title><link>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html</link><pubDate>Mon, 10 Mar 2008 01:16:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32879.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/03/10/32879.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32879.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32879.html</trackback:ping><description>&lt;A href="http://www.newsmth.net/bbstcon.php?board=CPlusPlus&amp;amp;gid=218259"&gt;原文&lt;/A&gt;&lt;BR&gt;本以为很简单，仔细阅读了一下 C++ 标准，发现内容还不少。总结了一下。&lt;BR&gt;&lt;BR&gt;要点：&lt;BR&gt;&lt;BR&gt;函数类型，函数名，变量名具有语言链接性，language linkage。&lt;BR&gt;&lt;BR&gt;语言链接性可能会影响到名字以及调用约定等，由实现决定。&lt;BR&gt;&lt;BR&gt;C++ 默认的语言连接性是 C++ 语言链接性。&lt;BR&gt;&lt;BR&gt;语言链接性仅作用于函数类型，函数名，变量名。&lt;BR&gt;&lt;BR&gt;不同语言链接性的函数类型是不同的类型，即便其余的地方都相同。&lt;BR&gt;&lt;BR&gt;语言链接性用链接性规格（linkage-specification）来声明，分有无大括号两种形式。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern string-literal { declaration-seq opt }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern string-literal declaration;&lt;BR&gt;&lt;BR&gt;所有的实现都必须支持 "C" 和 "C++" 链接性。&lt;BR&gt;&lt;BR&gt;链接性规格允许嵌套，此时最内层的那个起作用，但是并不建立作用域。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { extern "C++" { class A{}; } }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { class B:A{};}&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;因为不是作用域，B 可以看到 A&lt;BR&gt;&lt;BR&gt;如果C链接性施加到C++类成员和成员函数的类型，忽略C链接性，但是其余的地方依然有效，比如成员函数的参数。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;比如：&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { class A{ void f(void (*p)()){} }; }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;A 不是函数和变量，没有语言链接性&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;f 是 C++ 成员函数，忽略所指定的 C 链接性，如果在外层没指定别的，就是&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;C++链接性。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;p 具有 C 链接性。&lt;BR&gt;&lt;BR&gt;除了C++链接性的函数外，同一个函数不带链接性规格的声明的函数不能早于带的。&lt;BR&gt;如果前面声明了带链接性规格的形式，后面又出现了不带的形式，不受影响。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;比如 extern "C" void foo(); void foo(); 是可以的，反过来不行。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;但是 extern void foo(); extern "C++" void foo(); 可以。&lt;BR&gt;&lt;BR&gt;特定名字的 C 语言链接性的函数最多只能有一个，即便放在不同的 namespace 里，&lt;BR&gt;也是同一个函数，变量也是如此。这样的函数或变量也不能重复定义。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;namespace A { extern "C" void f(); }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;namespace B { extern "C" void f(); }&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;两个 f 是同一个&lt;BR&gt;&lt;BR&gt;有链接性规格的函数具有外部链接性。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;因此&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" void f();&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;static void f();&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;是错误的。&lt;BR&gt;&lt;BR&gt;链接性规格，带大括号的形式，不影响里面的声明是声明还是定义。&lt;BR&gt;单个声明的形式，视为 extern 限定符。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" int i; &amp;nbsp; &amp;nbsp; &amp;nbsp; 是声明；&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { int i; } &amp;nbsp; 是定义；&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" { extern int i; } 又是声明。&lt;BR&gt;&lt;BR&gt;单个声明形式的连接性规格，不能再指定存储类别。&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" static void f(); // error&lt;BR&gt;&lt;BR&gt;其他说明：&lt;BR&gt;因为语言链接性只跟链接性有关，因此 C 语言链接性的函数也可以有默认参数，&lt;BR&gt;函数的接口可以有引用。&lt;BR&gt;&lt;BR&gt;标准 C++ 库中的符号默认是 C++ 链接性。&lt;BR&gt;C++ 用的标准 C 库中的外部链接性符号可以是 C 或者 C++ 语言链接性，推荐后者。&lt;BR&gt;任何带有连续两个下划线的名字保留给实现用作同时具有 C 和 C++ 链接性的名字。&lt;BR&gt;&lt;BR&gt;标准 C 库中的任何函数签名都保留给实现用来做同时具有 C 和 C++ 链接性的名字。&lt;BR&gt;&lt;BR&gt;因为不同语言连接性的相同签名的函数类型算作不同类型，所以可以相互重载，不算同&lt;BR&gt;一个函数。&lt;BR&gt;//commented by ctrlz&lt;BR&gt;//这里应该是ill-formed. &lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C" int atexit(void (*f)(void)) &amp;nbsp; // 这里的 f 是 C 链接性&lt;BR&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;extern "C++" int atexit(void (*f)(void)) // 这里的 f 是 C++ 链接性&lt;BR&gt;&lt;BR&gt;类似的有两个原型的函数还有 bsearch，qsort 等带函数指针类型的参数的函数。&lt;BR&gt;&lt;BR&gt;以上都是 C++ 标准里的理论，落实到实践上，为了实现方便，很多编译器都把 C 和 &lt;BR&gt;C++ 链接性的函数的类型视为相同的类型，此时的链接性仅仅影响生成的名字，或者&lt;BR&gt;还可能影响异常规格，比如把 extern "C" 的函数默认视为 throw() 的等，并不影响&lt;BR&gt;调用约定。同样也为了实现的方便，C++ 所用的 C 库中的符号也都是 C 语言链接性的，&lt;BR&gt;因此这样的情况下，atexit, bsearch, qsort 等在这些编译器搭配的库中也就只有一种&lt;BR&gt;原型了。&lt;BR&gt;&lt;BR&gt;这种情况下，同样签名的 C 和 C++ 链接性的函数类型就算做同种类型了，下边的程序&lt;BR&gt;编译就会失败：&lt;BR&gt;//commented by ctrlz.&lt;BR&gt;//ill-formed&lt;BR&gt;void f(void (*p)())&lt;BR&gt;{&lt;BR&gt;}&lt;BR&gt;extern "C" void f(void (*q)())&lt;BR&gt;{&lt;BR&gt;}&lt;BR&gt;&lt;BR&gt;VC 和 gcc 在这一点上都是不符合标准的。&lt;BR&gt;&lt;BR&gt;链接性不应该影响函数的类型，就像普通函数的返回类型不影响函数的类型一样，从重载解&lt;BR&gt;析的角度看，如果接受以上的行为，势必影响重载解析的过程。&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32879.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>一个更改键盘映射的小工具</title><link>http://blog.vckbase.com/localvar/archive/2008/02/28/32806.html</link><pubDate>Thu, 28 Feb 2008 01:20:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/02/28/32806.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32806.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/02/28/32806.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32806.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32806.html</trackback:ping><description>倒霉lenovo居然认为page up/page down的使用频率高于home/end，上网找到了这个小工具，可以重新映射键盘（只改注册表，不驻留内存）。怕以后找不到了，放自己blog上备份一下。&lt;BR&gt;&lt;A href="http://www.mympc.org/down/1/2005-11-26_0111998067.html"&gt;原始链接&lt;/A&gt;&lt;BR&gt;&lt;A href="http://blog.vckbase.com/Files/localvar/kbmap.zip"&gt;我的备份&lt;/A&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32806.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>翻译：sqlite中原子提交的实现</title><link>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html</link><pubDate>Wed, 13 Feb 2008 01:47:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/32581.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/02/13/32581.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/32581.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/32581.html</trackback:ping><description>最近在实现一个类似数据库事务操作的东西，找到了&lt;A href="http://www.sqlite.org/atomiccommit.html"&gt;这篇关于sqlite事务实现的文章&lt;/A&gt;，觉得还不错。由于网上相关的中文资料很少，所以决定把它翻译过来。不过，等我翻译完了之后，发现有人已经&lt;A href="http://chensheng.net/p/sqlite/auto_commit_zh_cn.html"&gt;先我一步完成了&lt;/A&gt;，我对比了一下这两个译本，自认为我的翻译质量更高一点，故仍有必要把它也发布出来。 
&lt;H3&gt;1. 引言&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;像SQLITE这样支持事务的数据库的一个重要特性是&amp;#8220;原子提交&amp;#8221;。原子提交意味着，一个事务中的所有修改动作要么全都发生，要么一个都不发生。有了原子提交，对一个数据库文件不同部分的多次写操作，就会像瞬间同时完成了一样。当然，现实中的存储器硬件会把写操作串行化，并且写每个扇区都会花上那么一小段时间，所以，绝对意义上的&amp;#8220;瞬间同时完成&amp;#8221;是不可能的。但SQLITE的原子提交逻辑还是让整个过程看起来像那么回事。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE保证，即使事务执行过程中发生了操作系统崩溃或掉电，整个事务也是原子的。本文描述了SQLITE实现原子提交时所采用的技术。 
&lt;H3&gt;2. 对硬件的假设&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;虽然有的时候会使用闪存，但下文中，我们将把存储设备称为&amp;#8220;磁盘&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们假设对磁盘的写操作是以&amp;#8220;扇区&amp;#8221;为单位的，也就是说不可能直接对磁盘进行小于一个扇区的修改，要想进行这类修改，你必须把整个扇区读进内存，进行所需的修改，然后再把整个扇区写回去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;对真正&amp;#8220;磁盘&amp;#8221;来说，读写操作的最小单位都是一个扇区；但闪存有些不同，它们的最小读单位一般远小于最小写单位。SQLITE只关心最小写单位，所以，在本文中，我们说&amp;#8220;扇区&amp;#8221;的时候，指的是向存储器中写数据时的最小数据量。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.3.14版之前，SQLITE在任何情况下都认为一个扇区的大小是512字节，有一个编译期选项能改变这个值，但从未有人用更大一些的值测试过相关代码。直到不久以前，把这个值定为512都是合理的，因为所有的磁盘驱动器都在内部使用512字节的扇区。但最近，有人把磁盘扇区的大小提升到了4096字节，而且，闪存的扇区一般也是大于512字节的。由于这些原因，从3.3.14版开始，SQLITE的操作系统接口层提供了一种可以从文件系统获取真实扇区大小的方法。不过，到目前为止（3.5.0版），这一方法仍然只是返回一个硬编码的512字节，因为不论是win32系统还是unix系统，都没有一个标准的机制来获得实际的值。但这种方法给了嵌入式设备的提供商们根据实际情况进行调整的能力，也让我们未来在win32和unix上给出一个更有意义的实现成了可能。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE并不假设对扇区的写操作是原子的，它仅假设这种写是&amp;#8220;线性&amp;#8221;的。所谓线性是指：写一个扇区时，硬件总是从扇区一端开始，一个字节一个字节的写到另一端结束，中间不会后退，硬件可以从头向尾写，也可以从尾向头写。如果掉电发生时只写到了扇区的中间，则可能出现扇区一部分修改了而另一部分没被修改的情况。SQLITE在这里做的一个关键假设是：只要扇区被修改了，那么它的第一个字节和最后一个字节中的至少一个会被修改，也就是说，硬件绝不会从中间开始向两端写。我们不清楚这个假设是否总是对的，但它看起来是合理的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在上一段中，我们说&amp;#8220;SQLITE没有假设写扇区是原子的&amp;#8221;。默认情况下，这是正确的，但在3.5.0版中，我们增加了一个叫做&amp;#8220;虚拟文件系统（VFS）&amp;#8221;的接口，它是SQLITE和底层文件系统通讯的唯一路径。代码中包含了用于unix和windows的默认VFS实现，同时提供了一种在运行时创建新VFS实现的机制。在这个新的VFS接口中有一个称为&amp;#8220;xDeviceCharacteristics&amp;#8221;的方法，它通过询问文件系统来判断文件系统是否支持某些特性。如果文件系统支持某个特性，SQLITE就会试着利用这个特性进行某种优化。默认的xDeviceCharacteristics不会指出文件系统支持原子的写扇区操作，所以与此相关的优化都是关闭的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设操作系统会缓冲写操作，并且写操作会在数据被真正写到磁盘上之前返回。SQLITE还假设写操作会被操作系统记录下来。因此，SQLITE会在关键点上执行&amp;#8220;flush&amp;#8221;或&amp;#8220;fsync&amp;#8221;，并假设&amp;#8220;flush&amp;#8221;和&amp;#8220;fsync&amp;#8221;会等所有正在进行的&amp;#8220;写操作&amp;#8221;真正执行完毕后才返回。在某些版本的windows和unix上，&amp;#8220;flush&amp;#8221;和&amp;#8220;fsync&amp;#8221;原语会被打断，这非常不幸，在这些系统上，如果提交的过程中发生了掉电，SQLITE的数据库有可能崩溃掉，而SQLITE自己则对此无能为力。SQLITE假设操作系统能像广告宣传的那样完美，如果事实并非如此，你只好祈求老天保佑不要经常掉电了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设文件增长时，新增加的部分最初包含的是垃圾数据，然后它们会被实际的数据覆盖掉。换句话说，SQLITE假设文件大小的变化发生在文件内容变化之前。这是个悲观的假设，为了保证在从&amp;#8220;文件大小改变&amp;#8221;开始到&amp;#8220;文件内容写完&amp;#8221;为止的这段时间内，系统掉电不会导致数据库崩溃，SQLITE要做一些额外的工作。VFS的xDeviceCharacteristics也可能会指出文件系统总是先写数据后更新文件的大小，这种情况下，SQLITE可以跳过一些过于小心的数据库保护操作，从而减少一次提交所需的磁盘I/O数量。但目前windows和unix上的VFS实现都没有做这个假设。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设文件删除是原子的，至少从用户程序的角度来看要是这样。也就是说，如果SQLITE要删除一个文件，并且删除的过程中掉电了，那么电力恢复后，文件要么不能从文件系统中找到，要么它的内容和删除之前一模一样。如果文件还能从文件系统中找到，但内容被修改或清空了，那么数据库极有可能会崩溃。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设检测由宇宙射线、热噪声、驱动程序bug等引起的位错误（bit error）是操作系统和硬件的责任。SQLITE没有在数据库文件中增加任何冗余信息来检测或纠正这类问题。SQLITE假设它所读的数据与它上次所写的数据总是完全相同。 
&lt;H3&gt;3. 单文件提交&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们先来从整体上看看SQLITE在一个单独的数据库文件上操作时，要保证事务提交的原子性需要哪些步骤。为防止掉电时文件被破坏，文件格式在设计时也有相应考虑，相关细节和多数据库提交技术将在后续章节讨论。 
&lt;H4&gt;3.1. 初始状态&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下图给出了数据库连接刚刚打开时计算机的状态。图的最右侧是存储在磁盘上的数据，每个小格代表一个扇区，蓝色表示扇区存储的是原始数据；图的中间部分是操作系统的缓存，在当前的例子中，缓存是&amp;#8220;冷&amp;#8221;的，所以它的每个格都没有着色；最左侧是使用SQLITE的进程（译注：本文的作者可能更喜欢unix，所以在windows上，原文中的部分&amp;#8220;进程&amp;#8221;用&amp;#8220;线程&amp;#8221;替换一下会更好，我没有做这种替换，故需要您在阅读过程中结合上下文判断&amp;#8220;进程&amp;#8221;的具体含义）的内存，数据库连接刚刚创建，还没有读任何数据，所以用户的内存空间中什么也没有。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-0.gif"&gt; 
&lt;H4&gt;3.2. 获取一个&amp;#8220;读锁&amp;#8221;&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE写数据库之前，必须先读，这样它才能知道数据库中已经有些什么了。即使是单纯的追加数据，SQLITE也要先从sqlite_master表中读出数据库的表结构，从而知道如何去解析INSERT语句，以及新数据应该保存到文件的哪个位置。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;读操作的第一步是获取一个数据库文件的&amp;#8220;共享锁&amp;#8221;。这个共享锁允许两个或多个数据库连接同时读数据库文件，但不许其他数据库连接写这个文件。这个锁非常重要，因为，如果在读数据的过程中另一个连接写了数据，我们就可能读到一个新数据和旧数据的混合体，这会让其他连接的写操作失去原子性。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;请注意，共享锁是操作系统的磁盘缓存实现的，而不是磁盘本身。一般来说，文件锁仅仅是操作系统内核中的一些标志（细节取决于具体操作系统的接口层）。所以，当系统崩溃或掉电后，这个锁就自动消失了。并且，通常情况下，创建这个锁的进程退出后，锁也会自动消失。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-1.gif"&gt; 
&lt;H4&gt;3.3. 从数据库中读数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获得共享锁后，我们开始从数据库文件中读出数据。在这个例子中，由于我们假设最初的缓存是&amp;#8220;冷&amp;#8221;的，所以要先把数据从磁盘读到操作系统的缓存，再把它们从缓存复制到用户空间。后续的读操作，由于部分或全部数据可能已经在缓存中了，或许就只需要从缓存复制到用户空间这一步了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一般情况下，我们不会需要数据库文件的所有页（译注：页是SQLITE对数据进行缓冲的最小单位，但本文中有时它和扇区是一个意思，请注意结合上下文区分），所以我们读的只是它的一个子集。本例中，我们的数据库文件有8个页，而我们需要的是其中的3个。一个真实的数据库可能有数千个页，但每次查询要访问的一般只是其中很小的一部分。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-2.gif"&gt; 
&lt;H4&gt;3.4. 获取一个预定（Reserved）锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在对数据库做任何修改之前，SQLITE需要获得一个预定锁。预定锁和共享锁很像，它们都允许其他进程读数据库文件。并且，预定锁也可以和多个共享锁共存。但是，一个数据库文件某一时刻只能有一个预定锁，也就是只允许一个进程有写数据的意图。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;预定锁的目的是告诉整个系统：有一个进程要在不久的将来修改数据库文件了，但它目前还没有任何实际行动。由于仅仅是个&amp;#8220;意图&amp;#8221;，其他进程还可以继续自己的读操作，但是它们不能也有这个意图了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-3.gif"&gt; 
&lt;H4&gt;3.5. 创建回滚日志（Journal）文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在任何实质性的修改之前，SQLITE还需要创建一个独立的回滚日志文件，并把所有要被替换的数据库页的原始内容写到这个文件中去。实际上，日志文件将保存将数据库文件恢复到原始状态所需的全部信息。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志文件有一个不大的文件头（图中用绿色表示），它记录了数据库文件的原始大小。如果数据库文件因为修改变大了，我们仍然可以凭它来获得文件的原始大小。数据库页和它们的对应的页号会被放在一起写到日志文件中去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;创建新文件时，大多数操作系统（windows、linux、macOSX等）并不会立即向磁盘写数据。新文件一开始只存在于操作系统的缓存中，直到操作系统有空闲的时候，它才会真的去在磁盘上创建这个文件。这种方式让用户觉得文件创建非常快，起码比真的去做磁盘I/O快多了。在下图中，为了表示这一情形，我们只在操作系统缓存中画了这个日志文件。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-4.gif"&gt; 
&lt;H4&gt;3.6. 在用户空间中修改数据库&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;数据库页的原始内容保存到日志文件后，就可以在用户空间中修改了。每个数据库连接有一份私有的用户空间拷贝，所以这些修改只会被当前的连接看到，其他连接看到的仍然是操作系统缓存中未被修改的内容。在这种情况下，虽然有一个进程正在对数据库进行修改，其他进程仍然可以继续读数据库的原始内容。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-5.gif"&gt; 
&lt;H4&gt;3.7. 把日志文件&amp;#8220;刷&amp;#8221;到磁盘&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是把回滚日志文件的内容刷到具有持久性的存储器上。后面你会看到，这是让数据库能够在掉电情况下存活的关键之一。它可能要花不少时间，因为往持久性存储器上写东西一般是很慢的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;这一步通常比仅仅把回滚日志刷到磁盘上复杂的多。在大多数平台上，你要刷（flush或fsync）两次才行。第一次是日志文件的基本内容。然后修改日志文件的头部，以反应日志文件中实际的页面数。接着刷第二次，把文件头刷上去。至于为什么要修改文件头并多刷一次，我们将在后续章节讨论。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-6.gif"&gt; 
&lt;H4&gt;3.8. 获取一个独占锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了对数据库文件进行真正的修改，我们需要一个独占锁。获取这个锁需要两步，首先是获取一个待决（Pending）锁，然后再把它提升为独占锁。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;待决锁允许其他已经有了共享锁的进程继续读数据库文件，但它不允许创建新的共享锁。设计它的目的是为了避免一大堆读进程把写进程给饿到。系统中可能会有几十甚至上百个进程想读数据库文件，每个这样的进程都要经历一个&amp;#8220;获得共享锁、读数据、释放锁&amp;#8221;的过程。如果很多进程都想读同一个数据库文件，那么一个极有可能现象是：新进程总是在已有的进程释放共享锁之前获得一个新的共享锁。这样一来，数据库文件就上就总有共享锁了，要写数据的进程可能会一直没有机会得到自己的独占锁。通过禁止创建新的共享锁，待决锁解决了这个问题，已有的共享锁会逐渐被释放，最终，当它们全部被释放后，待决锁就可以升级到独占锁了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-7.gif"&gt; 
&lt;H4&gt;3.9. 更新数据库文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一旦获得独占锁，就可以保证没有其他进程在读这个数据库文件了，这时更新它就是安全的了。一般来说，这里的更新只会影响到操作系统磁盘缓存这一层，而不会影响磁盘上的物理文件。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-8.gif"&gt; 
&lt;H4&gt;3.10. 把变化刷到存储器&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了把数据库的变化写到持久性存储器，我们还要再刷一次。这也是保证数据库在掉电情况下不崩溃的关键。当然，向磁盘或闪存写数据实在是太慢了，这一步和3.7节中的刷日志文件加在一起会消耗掉SQLITE一次事务提交的绝大部分时间。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-9.gif"&gt; 
&lt;H4&gt;3.11. 删除日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把所有变化都安全的写到存储器上以后，回滚日志文件就可以删除了。这是提交事务的那个时间点。如果掉电或系统崩溃发生在这之前，后面将要介绍的恢复过程会让数据库文件回到修改之前的状态，就好像什么都没发生过一样。如果掉电或系统崩溃发生在日志文件被删除之后，那么所有的修改都会生效。所以，SQLITE对数据库的修改全部有效还是全部无效，实际上是取决于这个日志文件是否存在。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;删除文件不一定真的是原子操作，但从用户程序的角度来看，它却好像总是原子的。进程总可以询问操作系统&amp;#8220;这个文件存在吗？&amp;#8221;并等到是或否的回答。如果事务提交过程中发生了掉电，SQLITE就会问操作系统是否存在回滚日志文件，存在则事务是不完整的，需要回滚，不存在则说明事务确实成功提交了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE事务的实现依赖于回滚日志文件是否存在和用户程序眼中的原子的文件删除。所以，事务也是一个原子操作。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-A.gif"&gt; 
&lt;H4&gt;3.12. 释放锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;最后一步是释放独占锁，这样其他进程就又能访问数据库文件了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在下图中，我们看到，用户空间中的数据在锁被释放后就清除了。如果是较早版本的SQLITE，这是实际情况。但从最近几版开始，SQLITE不这么做了，因为下个操作可能还会用到它们。比起从操作系统的缓存或磁盘中读数据来，重用这些已经在本地内存中的数据的性能要高得多。再次使用它们之前，我们要先得到一个共享锁，然后再检查一下在我们没有锁的这段时间内是否有别的进程修改了数据库文件。数据库的第一页有一个计数器，每次对数据库进行修改时都会递增它。检查这个计数器，就能知道数据库是否被别的进程修改过了。如果修改过，就必须清除用户空间中的数据并把新数据读进来。但更大的可能是没有任何修改，这样就可以重用原有的数据，从而大幅提高效率。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_commit-B.gif"&gt; 
&lt;H3&gt;4. 回滚&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;原子提交看起来是瞬间完成的，但很明显，前面介绍的过程需要一定的时间才能完成。如果在提交过程中电源被切断，为了让整个过程看起来是瞬时的，我们必须回滚那些不完整的修改，并把数据库恢复到事务开始之前的状态。 
&lt;H4&gt;4.1. 如果出了问题&amp;#8230;&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;假设掉电发生在3.10节所讲的那一步，也就是把数据库变化刷到磁盘中去的时侯。电力恢复后，情况可能会像下图所示的那样。我们要修改三页数据，但只成功完成了一页，有一页只写了一部分，另一页则一点都没写。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;电力恢复后日志文件是完整的，这是个关键。3.7节中的操作就是为了保证在对数据文件做任何改变之前回滚日志的所有内容已经安全的写到持久性存储器中去了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-0.gif"&gt; 
&lt;H4&gt;4.2. &amp;#8220;热的&amp;#8221;回滚日志&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;任何进程第一次访问数据库文件之前，必须获得一个3.2节中描述的共享锁。然后，如果发现还有一个日志文件，SQLITE就会检查这个回滚日志是不是&amp;#8220;热的&amp;#8221;。我们必须回放热日志文件，从而把数据库恢复到一致的状态。只有在一个程序正在提交事务时发生掉电或崩溃的情况下，才会出现热日志文件。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志文件在符合以下所有条件时才是热的： &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件是存在的 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件不是空文件 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;数据库文件上没有预定锁 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
&lt;LI&gt;日志文件头中没有主日志文件的文件名，或者，如果有主日志文件名的话，主日志文件是存在的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;热日志文件告诉我们：之前有进程试图提交一个事务，但由于某种原因，这个提交没有完成。也就是说：数据库处于一种不一致的状态，使用之前必须修复（回滚）。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-1.gif"&gt; 
&lt;H4&gt;4.3. 获取数据库上的独占锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;处理热日志的第一步是获得数据库文件上的独占锁，这可以防止两个或更多的进程同时回放一个热日志。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-2.gif"&gt; 
&lt;H4&gt;4.4. 回滚不完整的修改&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获得了独占锁，进程就有权力修改数据库文件了。它从日志中读出页面的原有内容，然后把它们分别写回到其在数据库文件中的原始位置上去。前面说过，日志文件的头部记录了数据库文件在事务开始前的大小，如果修改让数据库文件变大了，SQLITE会使用这一信息把文件截断到原始大小。这一步结束之后，数据库文件就应该和事务开始前一样大，并且包含和那时完全一样的数据了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-3.gif"&gt; 
&lt;H4&gt;4.5. 删除热日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;日志中的所有信息都回放到数据库文件，并将数据库文件刷到磁盘（回滚时可能会再次掉电）以后，就可以删除热日志文件了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-4.gif"&gt; 
&lt;H4&gt;4.6. 继续前进，就像那个中断了的事务根本没发生过一样&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;回滚的最后一步是把独占锁降级为共享锁。此后，数据库的状态看起来就像那个中断了的事务根本没有开始过一样了。由于整个回滚过程是完全自动、透明的，使用SQLITE的那个程序根本就不会知道有一个事务中断并回滚了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_rollback-5.gif"&gt; 
&lt;H3&gt;5. 多文件提交&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;通过ATTACH DATABASE命令，SQLITE允许一个数据库连接使用多个数据库文件。当在一个事务中修改多个文件时，所有文件都会被原子的更新。换句话说，或者所有文件都会被更新，或者一个也不会被更新。在多个文件上实现原子提交比在单个文件上实现更复杂，本章将解释SQLITE是如何做到这一点的。 
&lt;H4&gt;5.1. 每个数据库一个日志&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当一个事务涉及了多个数据库文件时，每个数据库都有自己回滚日志，并且对它们的锁也是各自独立的。下图展示了三个数据库文件在一个事务中被修改的情况，它所描述的状态相当于单文件事务在第3.6节中的状态。每个数据库文件有各自的预定锁，它们将要被修改的那些页的原始内容已经写进回滚日志了，但还没有刷到磁盘上。用户内存中的数据已经被修改了，不过数据库文件本身还没有任何变化。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;相比之前，下图做了一些简化。在这张图上，蓝色仍然代表原始数据，粉红色仍然代表新数据。但上面没有画出回滚日志和数据库的页，并且也没有明确区分操作系统缓存中的数据和磁盘上的数据。所有这些在这张图上仍然适用，不过即使把它们画出来我们也学不到什么新的东西，所以，为了缩小图幅，我们把它们省略掉了。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-0.gif"&gt; 
&lt;H4&gt;5.2. 主日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;多文件提交中的下一步是创建一个&amp;#8220;主日志文件&amp;#8221;。这个文件的名字是最初的数据库文件名（也就是用sqlite3_open()打开的那个数据库，而不是之后附加上来的那些）加上后缀&amp;#8220;-mjHHHHHHHH&amp;#8221;。其中HHHHHHHH是一个32位16进制随机数，每次生成新的主日志文件时，它都会不同。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; （注意：上面一段中用来生成主日志文件名的方法是3.5.0版中使用的方法。这个方法并没有规范化，也不是SQLITE对外接口的一部分，在未来版本中，我们可能会修改它。） &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 主日志中没有与原始数据库页面内容相关的信息，它里面保存的是所有参与到这个事务中的回滚日志文件的完整路径。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 主日志生成完毕后，会被立即刷到磁盘上，中间没有任何别的操作。在unix系统上，主日志所在的目录，也会被同步一下，以确保掉电后它也会出现在这个目录下。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-1.gif"&gt; 
&lt;H4&gt;5.3. 更新回滚日志文件头&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是把主日志的路径记录到回滚日志的文件头中去，回滚日志创建时在文件头预留了相应的空间。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;主日志路径写到回滚日志文件头之前和之后，要分别把回滚日志的内容往磁盘上刷一次。这可能有些效率损失，但非常重要，而且，幸运的是，刷第二次时一般只有一页（最开始的那页）数据有变化，所以整个操作可能并没有想象的那么慢。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;这个操作大致相当于单文件提交时的第7步，也就是第3.7节中的内容。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-2.gif"&gt; 
&lt;H4&gt;5.4. 更新数据库文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把回滚日志刷到磁盘上后，就可以安全的更新数据库文件了。我们需要获得所有数据库文件上的独占锁，然后写数据，并把这些数据刷到磁盘上去。这一步相当于单文件提交时的第8、9和10步。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-3.gif"&gt; 
&lt;H4&gt;5.5. 删除主日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;下一步是删除主日志文件，这是多文件事务被实际提交的时间点。它相当于单文件提交时的第11步，也就是删除日志文件的那一步。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果掉电或系统崩溃发生在这之后，重启时，即使存在回滚日志文件，事务也不会被回滚。这里的区别在于回滚日志的文件头里面有主日志的路径。SQLITE只认为文件头中没有主日志文件路径的回滚日志（单文件提交的情况）或主日志文件仍然存在的回滚日志是&amp;#8220;热的&amp;#8221;，并且只会回放热的回滚日志。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-4.gif"&gt; 
&lt;H4&gt;5.6. 清理回滚日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;最后是删除所有的回滚日志文件，释放独占锁以便其他进程发现数据的变化。这一步对应的是单文件提交时的第12步。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;由于事务已经提交了，所以删除这些文件在时间上并不是非常紧迫。当前的实现是删除一个日志文件，并释放其对应的数据库文件上的独占锁，然后再接着处理下一个。今后，我们可能把它改成先删除所有日志文件，再释放独占锁。这里，只要保证删除日志文件在前，释放其对应的锁在后就行，文件被删除的顺序或锁被释放的顺序并不重要。 &lt;BR&gt;&lt;IMG src="/images/vckbase_com/localvar/1209/o_multi-5.gif"&gt; 
&lt;H3&gt;6. 提交中的更多细节&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第3章从总体上介绍了SQLITE原子提交的实现方法，但漏掉了几个重要的细节，本章将对它们进行一些补充说明。 
&lt;H4&gt;6.1. 总是日志中记录整个扇区&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在把数据库页面的原始内容写进回滚日志时，即使页面比扇区小，SQLITE也会把完整的扇区写进去。从前，SQLITE中的扇区大小是硬编码的512字节，而最小页面也是512字节，所以不会有什么问题。但从3.3.14版开始，SQLITE也支持扇区大小超过512字节的存储器了，所以，从这一版起，当某个扇区中的任何页面被写进日志时，这个扇区中的其它页面也会被一同写进去。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;掉电可能在写扇区时发生，总是记录整个扇区可以在这种情况下保证数据库不被破坏。例如，我们假设每个扇区有四个页面，现在2号页面被修改了，为了把变化写入这个页面，底层硬件，因为它只能写完整的扇区，也会把1、3、4号页面重新写一遍，如果写操作被打断，这三个页面的数据可能就不对了。为了避免这种情况，必须把扇区中的所有页面写到回滚日志中去。 
&lt;H4&gt;6.2. 日志文件中的垃圾数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;向日志文件末尾追加数据时，SQLITE一般悲观的假设文件系统会先用垃圾数据把文件撑大，再用正确的数据覆盖这些垃圾。换句话说，SQLITE假设文件体积先变大，之后才是写入实际内容。如果掉电发生在文件已经变大但数据还未写入时，回滚日志中就会包含垃圾数据。电力恢复后，另一个SQLITE进程会发现这个日志文件，并试图恢复它，这就有可能把垃圾数据拷贝到数据库文件，进而对其造成破坏。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为对付这个问题，SQLITE建立了两道防线。首先，SQLITE在回滚日志的文件头中记录了实际的页面数。这个数字一开始是0，所以，在回放一个不完整的回滚日志时，SQLITE会发现文件中没有包含任何页面，也就不会对数据库做任何修改。提交之前，回滚日志会被刷到磁盘上，以保证其中没有任何垃圾。之后，文件头中的页面数才会被改成实际的数值。文件头总是保存在一个单独的扇区去，所以，如果在覆盖它或把它刷到磁盘上时发生掉电，其它页面是不会被破坏的。注意回滚日志要往磁盘上刷两次：第一次是写页面的原始内容，第二次是写文件头中的页面数。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;上一段描述的是同步选项设置为&amp;#8220;full&amp;#8221;（PRAGMA synchronous=FULL）时的情形，这也是默认的设置。不过，当同步选项低于&amp;#8220;normal&amp;#8221;时，SQLITE只会刷一次日志文件，也就是修改完页面数后的那一次。由于（大于0的）页面数可能先于其它数据到达磁盘，这样做有一定的风险。SQLITE假设文件系统会记录写请求，所以即使先写数据后写页面数，页面数也可能会先被磁盘记录下来。所以，作为第二道防线，SQLITE在日志文件中为每页数据都记录了一个32位的校验码。回滚日志文件时，SQLITE会检查这个校验码，一旦发现错误，就会放弃回滚操作。要注意的是，校验码无法完全保证页面数据的正确性，数据有错误但校验码正确的概率虽然极小，却不是零.。不过，校验码机制至少让类似的事情看起来不那么容易发生了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在同步选项设置为&amp;#8220;full&amp;#8221;时，就没有必要用校验码了，我们只在同步选项低于&amp;#8220;normal&amp;#8221;时才需要它。然而，鉴于校验码是无害的，故不管同步选项如何设置，它们总是出现在回滚日志中的。 
&lt;H4&gt;6.3. 提交之前的缓存溢出&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第三章描述的过程假设提交之前所有的数据库变化都能保存在内存中。一般来说就是这样的，但特殊情况也会出现。这时，数据库变化会在事务提交之前用完用户缓存，需要把缓存中的内容提前写入数据库才行。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;操作之前，数据库连接处于第3.6步时的状态：原始页面的内容已经保存到回滚日志了，修改后的页面位于用户内存中。为了回收缓存，SQLITE执行第3.7到3.9步，也就是把回滚日志刷到磁盘上，获取独占锁，然后把变化写入数据库。但后续步骤在事务真正提交之前都有所不同。SQLITE会在日志文件的最后追加一个文件头（使用一个单独的扇区），独占锁继续保留，而执行流程将跳到第3.6步。当事务提交或再次回收缓存时，将重复执行第3.7和3.9步（由于第一次回收缓存时获得了独占锁且一直没有释放，3.8步将被跳过）。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;把预定锁提升为独占锁将降低并发度，额外的刷磁盘操作也非常慢，所以回收缓存会严重影响系统效率。因此，只要有可能，SQLITE就不会使用它。 
&lt;H3&gt;7. 优化&lt;/H3&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;对程序的性能分析显示，在绝大多数系统和绝大多数情况下，SQLITE把绝大部分时间消耗在了磁盘I/O上。所以，减少磁盘I/O的数量是最有可能大幅提升效率的方法。本章将介绍SQLITE在保证原子提交的前提下，为减少磁盘I/O而使用的一些技术。 
&lt;H4&gt;7.1. 在事务之间保持缓存数据&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在3.12节中，我们说过当释放共享锁时会丢弃所有已经在用户缓存中的数据库信息。之所以这样做，是因为没有共享锁的时候其他进程能够随意修改数据库文件的内容，从而导致已经缓存的数据过时。所以，每当一个新事务开始时，SQLITE都必须重新读一次以前读过的东西。这个操作并不像大家想象的那么糟糕，因为要重新读的数据极有可能仍在操作系统的缓存中，所谓的&amp;#8220;重读&amp;#8221;一般仅仅是把数据从内核空间拷贝到用户空间而已。不过，即使如此，也是需要一些时间的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从3.3.14版开始，我们在SQLITE中增加了一个机制来避免不必要的重读。这些版本中，释放共享锁后，用户缓存的页面继续保留。等到SQLITE启动下一个事务并获得共享锁后，它会检查是否有其他进程修改了数据库文件。如果自上次释放锁后有修改，用户缓存会被清空并重读。但一般不会有任何修改，所以用户缓存仍然有效，这样很多不必要的读操作就被避免了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;为了判断数据库文件是否被修改，SQLITE在文件头（第24到27字节）中使用了一个计数器，每个修改操作都会递增它。释放数据库锁之前，SQLITE会记下这个计数器的值，等到再次获得锁以后，它比较记录的值和实际的值，相同则重用已有的缓存数据，不同则清空缓存并重读。 
&lt;H4&gt;7.2. 独占访问模式&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;自3.3.14版开始，SQLITE中增加了&amp;#8220;独占访问模式&amp;#8221;。在这种模式下，SQLITE会在事务提交后继续保留独占锁。这样一来，其他进程就不能访问数据库了。不过，由于大多数的部署方案都只有一个进程访问数据库，所以一般不会有什么问题。独占访问模式让以下三个减少磁盘I/O的方法成为了可能： 
&lt;OL&gt;
&lt;LI&gt;除了第一个事务，不必每次递增数据库文件头中的计数器。这通常意味着在数据库文件和回滚日志中各自少刷一次1号页面。 
&lt;LI&gt;因为没有别的进程能访问数据库，所以没必要每次启动事务时检查计数器和清空用户缓存。 
&lt;LI&gt;事务结束后可以截断（译注：把文件长度设置为0字节）回滚日志文件，而不是删除它。在很多操作系统上，截断比删除快的多。 &lt;/LI&gt;&lt;/OL&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;第三项优化，也就是用截断代替删除，并不要求一直拥有独占锁。理论上说，总是实现它，而不是只在独占访问模式下实现它是可能的，也许我们会在未来版本中让其成为现实。不过，到目前为止（3.5.0版），这项优化仍然只在独占访问模式下有效。 
&lt;H4&gt;7.3. 不记录空闲页面&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从数据库中删除数据时，那些不再使用的页面会被加到&amp;#8220;空闲页表&amp;#8221;里去。之后的插入操作将首先使用这些页面，而不是扩大数据库文件。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;一些空闲页面中也有重要数据，比如说其他空闲页面的位置等等。但大多数空闲页面的内容没有用，我们把这些页面称为&amp;#8220;叶页&amp;#8221;。修改叶页的内容对数据库没有任何影响。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;由于叶页的内容没用，SQLITE不会把它们在提交过程的第3.5步中记录到回滚日志里去。也就是说，修改叶页，但不在回滚过程中恢复它们对数据库无害。同样的，一个新叶页的内容既不会在第3.9步中写入数据库也不会在第3.3步中被读出来。在数据库文件有空闲空间时，这项优化大幅减少了磁盘I/O的数量。 
&lt;H4&gt;7.4. .单页更新和原子扇区写&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从3.5.0版开始，新的VFS接口包含了一个名叫xDeviceCharacteristics的方法，它可以报告底层存储器是否支持一些特性。这些特性中，有一个是&amp;#8220;原子扇区写&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们前面说过，SQLITE假设写扇区是线性的，而不是原子的。线性写从扇区的一端开始，逐字节写到另一端结束。如果在线性写的中间发生掉电，则可能扇区的一端被修改了，另一端却保持不变。但在原子写的情况下，扇区或者被完全更新了，或者完全没有变化。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们相信大多数现在磁盘驱动器实现了原子扇区写。掉电时，驱动器使用电容中的电能和（或）盘片旋转的动能完成正在进行的操作。然而，在系统写调用与磁盘电子元件之间存在太多的层次，所以我们在Unix和windows的默认VFS实现上做了一个保守的假设，认为写扇区不是原子的。另一方面，能对其使用的文件系统有更多发言权的设备厂商，如果它们的硬件确实支持原子扇区写，也许会选择打开xDeviceCharacteristics中的这个选项。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;当写扇区是原子的、数据库页面和扇区一样大，而且数据库的变化只涉及到一个页面时，SQLITE会跳过整个记日志和同步过程，直接把修改后的页面写到数据库文件上。数据库文件第一页上的修改计数器也会独立修改，因为即使在更新它之前掉电也是无害的。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;译注：个人认为，如果硬件不支持原子扇区写，是无法在软件层次上实现绝对意义上的原子提交的。 
&lt;H4&gt;7.5. 支持安全追加的文件系统&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3.5.0版加入的另一项优化措施是基于文件系统的&amp;#8220;安全追加&amp;#8221;功能的。SQLITE假设向文件（特别是回滚日志文件）追加数据时，文件大小的改变早于文件内容增加。所以，如果掉电发生在文件变大之后，数据写完之前，文件中就会包含垃圾数据。也可以通过VFS中的xDeviceCharacteristics方法指出文件系统支持&amp;#8220;安全追加&amp;#8221;功能，这意味着内容的增加早于大小的改变，所以掉电或系统崩溃不可能向日志文件中引入垃圾。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;文件系统支持安全追加时，SQLITE总是在日志文件头的页面数字段中填入-1，表示回滚时要处理的页面数应该根据日志文件的大小自动计算。这个-1不会被修改，所以提交时，我们可以不用单独刷一次日志文件的第一页。而且，当回收缓存时，也没有必要在日志文件末尾再写一个新的文件头了，我们只要继续在已有的日志文件上追加新页面即可。 
&lt;H3&gt;8. 对原子提交的测试&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们作为SQLITE的开发者，对其在掉电和系统崩溃时的健壮性充满自信，因为，我们的自动测试过程在模拟的掉电故障下，对它的恢复能力进行了非常多的检测。我们把这种模拟的故障称为&amp;#8220;崩溃测试&amp;#8221;。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;崩溃测试使用了一个修改过的VFS，以便模拟掉电或崩溃时可能出现的各种文件系统错误。它可以模拟出没有完整写入的扇区、因为写操作没有完成而包含垃圾数据的页面、顺序错误的写操作等，这些错误在测试场景的各个路径点上都会出现。崩溃测试不停地执行事务，让模拟的掉电或系统崩溃发生在各个不同的时刻，造成各种不同的数据损坏。在模拟的崩溃事件发生之后，测试程序重新打开数据库，检测事务是否完全完成或者（看起来）根本没有启动，也就是数据库是否处于一个一致的状态。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE的崩溃测试帮助我们发现了恢复机制中的很多小问题（现在都已经修复了）。其中的一部分非常隐晦，单单通过代码检查和分析可能是发现不了的。这些经验让SQLITE的开发者相信：那些没有使用类似崩溃测试的数据库系统，非常有可能包含在系统崩溃或掉电时导致数据库损坏的BUG。 
&lt;H3&gt;9. 可能发生的问题&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;虽然SQLITE的原子提交机制本身是健壮的，但它却有可能被恶意的对手或不那么完善的操作系统实现给打垮。本章将介绍几个可能在掉电或系统崩溃时导致数据库损坏的情形。 
&lt;H4&gt;9.1. 有问题的锁&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE使用文件系统的锁来保证某一时刻只有一个进程和数据库连接可以修改数据库。文件系统的锁机制是在VFS层实现的，并且在每种操作系统上都有所不同。SQLITE自身的正确性依赖于这个实现的正确性。如果它出了问题，导致两个或更多进程能同时修改一个数据库文件，肯定会严重损坏数据库。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;有人向我们报告说windows的网络文件系统和（Unix的，译注）NFS的锁都有些问题。我们验证不了这些报告，但是考虑到在网络文件系统上实现一个正确的锁的难度，我们也无法否定它们。由于网络文件系统的效率也很低，所以我们建议你最好是避免在其上使用SQLITE。如果一定要这么做的话，请考虑使用一个附加的锁机制来保证即使文件系统自身的锁机制不起作用时，也不会出现多个进程同时写一个数据库文件的情况。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;苹果Mac OS X计算机上预装的SQLITE进行了一个扩展，可以在苹果支持的所有网络文件系统上使用一个替代的加锁策略。只要所有进程使用统一的方式访问数据库文件，这个扩展就工作的很好。但不幸的是，这些加锁机制是相互独立的，如果一个进程用AFP锁，另一个用点文件（dot-file）锁，那这两个进程就可能发生冲突，因为AFP锁并不能禁止点文件锁，反之亦然。 
&lt;H4&gt;9.2. 不完整的刷磁盘操作&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在第3.7节和3.10节中你已经看到，SQLITE要把系统缓存刷到磁盘上。在unix系统上，这是用fsync()系统调用来完成的，windows上则是用FlushFileBuffers()。可是，我们收到的报告显示，很多系统上的这些接口没有广告宣传的那么好。我们听说，在一些windows版本上，通过修改注册表，可以完全禁用FlushFileBuffers()；而linux的某些历史版本中的fsync仅仅是个什么也不干的空操作。我们还知道，即使是在FlushFileBuffers()或fsync()可以正常工作的系统上，IDE磁盘控制器也经常会在数据仍处在自己的缓存中时，撒谎说数据已经到达磁盘表面了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;在苹果的系统上，如果你把fullsync选项打开（PRAGMA fullsync=ON），它可以保证数据确实刷到磁盘上了。Fullsync本身就很慢，而fullsync的实现还需要重置磁盘控制器，这会让其他根本不相关的磁盘I/O也变慢，所以我们不建议你这样做。 
&lt;H4&gt;9.3. 文件删除只完成了一半&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE假设从用户程序的角度看文件删除是原子操作。如果删除文件时掉电，电力恢复后，SQLITE期望这个文件或者不存在，或者是一个完整的、和删除前一模一样的文件。如果操作系统做不到这一点，事务就有可能不是原子的。 
&lt;H4&gt;9.4. 文件中的垃圾&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SQLITE的数据库文件是普通的文件，其它用户程序也可以打开它并任意的往里面写数据，一些流氓程序就可能这样做。垃圾数据的来源也可能是操作系统或磁盘控制器的BUG，尤其是那些会在掉电时触发的BUG。对此类问题，SQLITE无能为力。 
&lt;H4&gt;9.5. 删除或重命名热日志文件&lt;/H4&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果发生了掉电或崩溃，并且生成了热日志文件，那么，在另一个SQLITE进程打开它和数据库文件并完成回滚之前，这两个文件的名字绝对不能改变。在第4.2步时，SQLITE会在打开的数据库文件所在的目录下，寻找热日志文件，这个文件的名字是从数据库文件名派生而来的。所以，只要这两个文件中的任何一个被移走或改名，就会找不到热日志，也就不会进行回滚。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;我们认为SQLITE恢复过程的失败模式一般是这样的：发生了掉电；电力恢复后，一位好心的用户或者系统管理员开始清点损失；他们发现有一个名为&amp;#8220;important.data&amp;#8221;的文件，他们可能很熟悉这个文件，所以没有对其进行任何操作；但崩溃后，磁盘上还有一个名为&amp;#8220;important.data-journal&amp;#8221;的热日志文件，用户把它删除了，因为他们认为这个文件是系统中的垃圾。防止此类事件的唯一方法可能就是加强用户教育了。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;如果有多个链接（硬链接或符号链接）指向一个数据库文件，那么生成的日志文件会依据打开数据库文件时使用链接名来命名。如果发生了崩溃，并且下次打开数据库时使用了另一个链接，则也会因为找不到热日志文件而不进行回滚。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;某些时候，掉电会导致文件系统出错，以致新更改的文件名无法记录，这时，文件就会被移动到&amp;#8220;/lost+found&amp;#8221;目录下。为防止此类错误，SQLITE会在同步日志文件的同时，打开并同步一下这个文件所在的目录。但是，一些八竿子打不着的程序，在数据库文件所在目录下创建其他文件的操作，也可能会导致文件被移动到&amp;#8220;/lost+found&amp;#8221;里去，这是SQLITE控制不了的，所以SQLITE对它也没什么办法。如果你正在使用此类名字空间易被损坏的文件系统（我们相信大多数现代的日志文件系统没有此问题），我们建议你把SQLITE的数据库文件放在单独的子目录中。 
&lt;H3&gt;10. 总结和展望&lt;/H3&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;不论是过去还是现在，总有人能发现一些SQLITE原子提交机制的失败模式，开发者也不得不为此做一些补丁。但这类事情发生的已经越来越少了，失败模式也变得越来越隐晦。不过，如果藉此认为SQLITE的原子提交逻辑已经无懈可击了，肯定是相当愚蠢的。开发者们能承诺的只是尽量快速的修复新发现的BUG。 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;同时，我们也在寻找新的方法来优化这个提交机制。在Linux、MacOSX和windows上，当前的VFS实现都做了悲观的假设。也许在与一些熟悉这些系统工作原理的专家交流之后，我们能放宽一些限制，让它跑得更快些。特别的，我们猜测大部分现代文件系统已经具有了&amp;#8220;安全追加&amp;#8221;和&amp;#8220;原子扇区写&amp;#8221;这两个特性，但在确认之前，我们仍会保守的做最坏假设。&lt;/LI&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/32581.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>再记自己的两个常识性错误</title><link>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html</link><pubDate>Tue, 08 Jan 2008 03:58:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/31689.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2008/01/08/31689.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/31689.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/31689.html</trackback:ping><description>&lt;P&gt;1. WSAStartup只要每个进程调用一次就行了&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 不知为什么, 几年以来，我一直认为要为每个使用网络的线程调一次. 直到今天才发现弄错了, 按说我一直是仔细阅读msdn的, 唉! 不过为每个线程调一次只是多余的, 并不是错误的, 也许这就是我一直没有注意到它的原因吧.&lt;BR&gt;2. do while循环中的continue会跳到哪里&lt;/P&gt;
&lt;DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid"&gt;
&lt;DIV&gt;&lt;SPAN style="COLOR: #0000ff"&gt;do&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN id=Codehighlighter1_3_42_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"&gt;&lt;/SPAN&gt;&lt;SPAN id=Codehighlighter1_3_42_Open_Text&gt;&lt;SPAN style="COLOR: #000000"&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;//&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&amp;nbsp;①&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;++&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;continue&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;//&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&amp;nbsp;②&lt;/SPAN&gt;&lt;SPAN style="COLOR: #008000"&gt;&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;}&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;while&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;(&amp;nbsp;i&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;10&lt;/SPAN&gt;&lt;SPAN style="COLOR: #000000"&gt;&amp;nbsp;);&lt;/SPAN&gt;&lt;/DIV&gt;&lt;/DIV&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一直认为是①, 今天正在写的程序出错了才发现是②. 老天保佑以前的程序不出错吧&lt;IMG height=20 src="/Emoticons/QQ/02.gif" width=20 border=0&gt;. 这个错误一直没发现的原因有两点，一是我用do while循环比较少, 里面有continue的更少; 二是自己偷懒了, 想当然了, 其实以前怀疑过它的结果的, 但觉得①更符合逻辑就没有深究.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 犯了错误总是比较郁闷的, 不过能在一个上午认识到这样两个错误，也算收获不小了。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/31689.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>Windows下配置SVN 1.4.5 + APACHE 2.2.6使用域认证</title><link>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html</link><pubDate>Thu, 20 Dec 2007 06:28:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/31427.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2007/12/20/31427.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/31427.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/31427.html</trackback:ping><description>&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其实在网上搜索这个主题，已经有很多文章了，而且Subversion和TortoiseSVN的文档上也有相关介绍。但在我自己配置的过程中，发现它们好像都不完全对。所以我觉得有必要把自己摸索的过程写出来，供大家参考。不过已经有那么多&amp;#8220;前车之鉴&amp;#8221;了，我的方法是否真的有用，只能靠老天保佑了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 要想配置成功，首先要保证Apache、Svn和mod_auth_sspi这几个模块的版本是匹配的。我最开始就是在这上面栽的跟头。Apache有很多个版本（以2.0.x和2.2.x最常见），作为对应，每个版本的svn都有一些子版本与其匹配。例如1.4.5版的svn就有针对2.0.x和2.2.x的两个子版本。不幸的是，网上搜到的svn下载链接多是指向针对Apache 2.0.x的那个子版本，当把它用在最新版（目前是2.2.6）的Apache上时，出问题就是必然的了。实际上，当使用2.2.x版的Apache时，我们应该到&lt;A href="http://subversion.tigris.org/servlets/ProjectDocumentList?expandFolder=91&amp;amp;folderID=9246" target=_blank&gt;这里&lt;/A&gt;，点击左侧的文件夹&lt;A href="http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100&amp;amp;expandFolder=8100&amp;amp;folderID=9246" target=_blank&gt;Windows Apache 2.2.x&lt;/A&gt;（等以后有了新版的apache，可能就是其它对应的文件夹了），然后在右侧的文件列表中下载对应得svn（我下载的是&lt;A href="http://subversion.tigris.org/downloads/1.4.5-win32/apache-2.2/svn-win32-1.4.5.zip" target=_blank&gt;svn-win32-1.4.5.zip&lt;/A&gt;）。mod_auth_sspi我们也下载针对2.2.x版apache的那个就可以了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 以下是我的安装配置过程，它是针对apache2.2.6和svn1.4.5的，如果你用的是其它版本，可能一些细节上会有所不同。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 分别安装apache和svn（svn就是把压缩包解开就行），然后把svn\bin文件夹下的mod_dav_svn.so、mod_authz_svn.so、libdb44.dll和intl3_svn.dll拷贝到apache的modules文件夹下，mod_auth_sspi中的mod_auth_sspi.so也拷贝到那去。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后是修改apache的配置文件httpd.conf，经过我的试验，最后确定使用下面配置文件就行了（其中背景标红的内容你可能需要根据你的实际情况进行修改）。&lt;/DIV&gt;
&lt;TABLE style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="75%" bgColor=#f1f1f1 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P style="MARGIN: 5px; LINE-HEIGHT: 150%"&gt;&lt;CODE&gt;&lt;SPAN style="COLOR: #000000"&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ThreadsPerChild&lt;/SPAN&gt; 250&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;MaxRequestsPerChild&lt;/SPAN&gt; 0&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerRoot&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;C:/Program Files/Apache Software Foundation/Apache2.2&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerName&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;svnserver.mydomain.net&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;8080&lt;/FONT&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerSignature&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;Off&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ServerTokens&lt;/SPAN&gt; Prod&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;DocumentRoot&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"htdocs"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Listen&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;8080&lt;/FONT&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; sspi_auth_module modules/mod_auth_sspi.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; auth_basic_module modules/mod_auth_basic.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; auth_digest_module modules/mod_auth_digest.so&lt;BR&gt;#&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; authn_file_module modules/mod_authn_file.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; authz_svn_module modules/mod_authz_svn.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dir_module modules/mod_dir.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; deflate_module modules/mod_deflate.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; mime_module modules/mod_mime.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; setenvif_module modules/mod_setenvif.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dav_module modules/mod_dav.so&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LoadModule&lt;/SPAN&gt; dav_svn_module modules/mod_dav_svn.so&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Directory&lt;/SPAN&gt; /&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;Options&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;FollowSymLinks&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AllowOverride&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;None&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Directory&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt; dir_module&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;DirectoryIndex&lt;/SPAN&gt; index.html&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;ErrorLog&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;e:/svn/server.log&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;LogLevel&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff0000"&gt;error&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000ff"&gt;DefaultType&lt;/SPAN&gt; text/plain&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt; mime_module&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;TypesConfig&lt;/SPAN&gt; conf/mime.types&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-compress .Z&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-gzip .gz .tgz&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-x509-&lt;SPAN style="COLOR: #ff0000"&gt;ca&lt;/SPAN&gt;&lt;SPAN style="COLOR: #ff0000"&gt;-&lt;/SPAN&gt;cert .crt&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AddType&lt;/SPAN&gt; application/x-pkcs7-crl .crl&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;IfModule&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&lt;FONT color=#000000&gt;# 注意&amp;#8220;/svn/&amp;#8221;中最后的斜杠是必须的, 否则列不出版本库列表&lt;BR&gt;# 访问时的url也要带着它, 想要去掉它可搜索RedirectMatch&lt;/FONT&gt;&lt;BR&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Location&lt;/SPAN&gt; &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;/svn/&lt;/FONT&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# configure SVN&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;DAV&lt;/SPAN&gt; svn&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SVNListParentPath &lt;SPAN style="COLOR: #ff0000"&gt;on&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 版本库的根目录&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SVNParentPath &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;e&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;svn&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 权限控制文件&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AuthzSVNAccessFile &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;e&lt;SPAN style="COLOR: #0000cc"&gt;:&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;svn/authz&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 认证时的提示信息(中文不好使)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AuthName&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff00ff"&gt;"&lt;FONT style="BACKGROUND-COLOR: #ff0000" color=#000000&gt;My Subversion&lt;/FONT&gt;"&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 使用域认证&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;AuthType&lt;/SPAN&gt; SSPI&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIAuth &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIAuthoritative &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 指定使用那个域&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIDomain &lt;FONT style="BACKGROUND-COLOR: #ff0000"&gt;mydomain.net&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 是否省略掉用户id的域名部分(好像只是影响svn的一些日志记录)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIOmitDomain &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 是否允许非IE客户端(必须打开)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIOfferBasic &lt;SPAN style="COLOR: #ff0000"&gt;On&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 基本认证(非域认证方式)具有更高的优先级?&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIBasicPreferred &lt;SPAN style="COLOR: #ff0000"&gt;Off&lt;/SPAN&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 用户名大小写&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SSPIUsernameCase lower&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 用户必须通过认证&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="COLOR: #0000ff"&gt;Require&lt;/SPAN&gt; valid-&lt;SPAN style="COLOR: #0000ff"&gt;user&lt;/SPAN&gt;&lt;BR&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;lt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;/&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;Location&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;&amp;gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后如果大家觉得手工编辑那个权限控制文件（authz）很麻烦的话，也有一个取巧的办法，就是使用visualsvn server，虽然它目前还不支持域认证，但是我们可以借用它的权限管理界面。操作如下（假设版本库的根目录是e:\svn，并且权限控制文件的名字这时必须用authz）：&lt;BR&gt;1）&amp;nbsp;按前面的操作安装好apache和svn，但不要启动apache&lt;BR&gt;2）&amp;nbsp;把e:\svn改名为e:\svn1&lt;BR&gt;3）&amp;nbsp;&lt;A href="http://www.visualsvn.com/server" target=_blank&gt;下载&lt;/A&gt;并安装visualsvn server，安装时指定版本库根目录为e:\svn&lt;BR&gt;4）&amp;nbsp;停掉并禁用visualsvn server的服务（VisualSVNServer），删除e:\svn&lt;BR&gt;5）&amp;nbsp;把e:\svn1的名字改回e:\svn&lt;BR&gt;6）&amp;nbsp;启动apache&lt;BR&gt;7）&amp;nbsp;启动visualsvn server的管理界面，把要使用这个版本库的所有人的域帐号都添加到它的用户列表中去（密码不会被实际使用，随便设或留空都行）。&lt;BR&gt;8）&amp;nbsp;万事ok了，设置权限吧！&lt;BR&gt;
&lt;DIV&gt;&lt;BR&gt;ps: 2008-05-23&lt;BR&gt;tortoisesvn(1.4.8版)文档中关于使用多认证源的描述中有一个错误，其中的&lt;FONT color=#ff0000&gt;AuthAthoritative&lt;/FONT&gt;&lt;FONT color=#000000&gt;和&lt;/FONT&gt;&lt;FONT color=#ff0000&gt;AuthAuthoritative&lt;FONT color=#000000&gt;都应该改成&lt;/FONT&gt;&lt;FONT color=#ff0000&gt;AuthBasicAuthoritative&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT color=#000000&gt;。另外，多认证源还要求域用户登录时必须用&amp;#8220;domain\user&amp;#8221;的形式，只输user部分就会用其他认证方式。所以，如果你按我前面的描述用了visual svn server，增加多认证源后，域用户的密码就千万不要留空了，因为那样不用密码就能登录了。&lt;BR&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/31427.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>介绍一下Subversion</title><link>http://blog.vckbase.com/localvar/archive/2007/12/18/31344.html</link><pubDate>Tue, 18 Dec 2007 02:42:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2007/12/18/31344.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/31344.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2007/12/18/31344.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/31344.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/31344.html</trackback:ping><description>&lt;DIV align=left&gt;本来是发在公司内刊上的，现在拿来这里凑个数。&lt;/DIV&gt;
&lt;DIV align=left&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;DIV align=left&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前一段时间，公司讨论统一配置管理工具时，我推荐了svn（subversion）。照理说，在公司已经使用了vss、cvs和ClearCase三种工具的情况下，再提一种基本没人用过的新工具不是什么明智的选择。但我确实觉得svn的优点很突出，值得一荐。下面我就对svn进行一下简单介绍，让各位同事对其有一个初步的了解。&lt;/DIV&gt;
&lt;H4&gt;1.&amp;nbsp;与其他工具的对比&lt;/H4&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; vss是我用的最多的配置工具，所以我相信每个用过vss的人都会对它的离线操作功能头疼不已。虽然它允许在与服务器断开的情况下修改文件，但重新连接后必须非常小心的处理每个文件，一旦出错，就会造成不小的麻烦。svn则没有这个问题。二者更详细的区别我会在下一节说明，这里就不多啰嗦了。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; svn相对cvs是有明显优势的，因为svn的设计目标之一就是&amp;#8220;一个更好的cvs&amp;#8221;。而且，从众多开源项目的反映看，它也确实达到了这个目标：06年，最大的开源网站SourceForge开始支持svn；KDE和GNOME的开发团队也已经换用svn；如果大家多注意一下的话，还会发现更多著名的开源项目使用了svn。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ClearCase我没有实际使用过，按说应该无权评价。不过，我没有用过它的原因是因为它太难用了，曾经学过几天，但后来觉得太过复杂，就转向vss了。相比之下，svn是比较简单的，我大概花了一两天的时间就可以完成基本操作了，用的比较熟练也不过两周。另外就是ClearCase要钱，而svn是免费的。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下表是这几种工具的对比，列出的功能都是我比较关心的。更详细的比较，大家可以看看&lt;A href="http://better-scm.berlios.de/comparison/"&gt;http://better-scm.berlios.de/comparison/&lt;/A&gt;。&lt;/DIV&gt;
&lt;DIV&gt;
&lt;TABLE cellSpacing=1 cellPadding=1 width="75%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=center&gt;&lt;STRONG&gt;项目&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;&lt;STRONG&gt;Vss&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;&lt;STRONG&gt;Clearcase&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;&lt;STRONG&gt;Cvs&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;&lt;STRONG&gt;svn&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;文件/目录的重命名或移动&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是?&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;否&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;原子提交&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;否&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;否&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;变更集&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;否&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;可间接实现&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;否&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;中文支持&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;未知&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;差&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;visual studio集成&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;eclipse集成&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;未知&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;未知&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;http访问&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;8.0支持&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;差&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;离线操作&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;差&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;未知&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;权限管理&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;较差&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;是&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;易用性&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;简单&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;复杂&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;较简单&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;较简单&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=left&gt;&amp;nbsp;授权&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;商业&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;商业&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;GPL&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;P align=center&gt;Apache/BSD&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/DIV&gt;
&lt;H4&gt;2.&amp;nbsp;svn能解决哪些问题&lt;/H4&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 一直以来，XXXX项目都是使用vss作为配置管理工具的，但应用过程中我也越来越感觉到vss满足不了项目的需要，所以决定在项目组内试用svn，并在合适的时机完全转换到svn。具体说来，svn可以解决以下几个（包括但不限于&lt;IMG height=20 src="/Emoticons/QQ/smile.gif" width=20 border=0&gt;）问题。&lt;/DIV&gt;
&lt;H5&gt;2.1.&amp;nbsp;获取某个以前的版本&lt;/H5&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 我想获得三天前签入的某个版本怎么办？在vss中，如果没有打标签，那可能就只有一个文件一个文件的去找了。规定每次签入都打个标签或许是个解决办法，不过这项规定能否很好的执行就是另一个问题了。在svn中，每次签入（准确地说应该是&amp;#8220;提交&amp;#8221;）都会为整个代码库生成一个确定的版本，所以能够轻松完成此任务。&lt;/DIV&gt;
&lt;H5&gt;2.2.&amp;nbsp;自动递增版本号&lt;/H5&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; XXXX发布后，现场经常会反馈回来一些问题。但我发现这些问题中有不少是由于程序版本不匹配或没有用最新的程序造成的。所以我就想在编译过程中实现一个自增版本号的功能，每次修改后，能自动生成一个版本号，并且能把它显示在程序的关于对话框等位置，这样工程人员就能直接把这些原因造成的问题排除掉了。让我失望的是vss做不到这一点，虽然它有&amp;#8220;关键字扩展（keyword expansion）&amp;#8221;功能，但实现不了我的需求。而如果要求每次编译前手工修改版本号，就是另外一个能否切实执行的问题，我对这类问题的总是很悲观的。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 前面已经说过，在svn中，每次签入（提交）都会为整个代码库生产一个版本。生成它的同时，svn还会为其指定一个递增的修订号，利用这个修订号和一个配套的工具（SubWCRev)，就可以做到自动生成版本号了。&lt;/DIV&gt;
&lt;H5&gt;2.3.&amp;nbsp;变更集&lt;/H5&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 作为项目经理或设计人员，大家可能还会经常希望知道某个开发人员的某次签入到底修改了哪些内容，这一点在vss中也是做不到的。而在svn中，只要比较签入前的版本和签入后的版本就行了。实际上，在svn中，你可以比较任意两个版本之间的区别，甚至能追查到一个文件中每行代码的责任人。&lt;/DIV&gt;
&lt;H5&gt;2.4.&amp;nbsp;离线操作&lt;/H5&gt;
&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这个在前面已经说过了，再提一次是想强调一下我有多希望配置管理工具支持此功能。&lt;/DIV&gt;
&lt;H3&gt;3.&amp;nbsp;相关工具和网站&lt;/H3&gt;
&lt;DIV&gt;&lt;STRONG&gt;TortoiseSvn：&lt;/STRONG&gt;是windows下功能最强、最实用的svn客户端。它与资源管理器的右键菜单集成，可以管理包括源代码在内的任何文件。它的&amp;#8220;图标叠加（Icon Overlay）&amp;#8221;功能，可以让我们从文件和文件夹的图标中直观的看出它们的状态，如是否被修改等。另外，前面提到的SubWCRev就是它的一部分。&lt;BR&gt;&lt;STRONG&gt;AnkhSvn：&lt;/STRONG&gt;一个visual studio的svn插件，支持svn的大部分功能，并且比较好用。&lt;BR&gt;&lt;STRONG&gt;VisualSvn：&lt;/STRONG&gt;也是一个visual studio的svn插件，应该比AnkhSvn好一些，不过它是商业软件。&lt;/DIV&gt;
&lt;DIV&gt;&lt;STRONG&gt;VisualSvn Server：&lt;/STRONG&gt;windows下svn+apache的组合，提供了一个mmc的管理界面，喜欢gui界面的人的福音。而且，虽然它和VisualSvn是同一家公司的产品，但它是免费的。&lt;BR&gt;&lt;STRONG&gt;Subclipse：&lt;/STRONG&gt;eclipse的svn插件，也已经很成熟，据说使用风格和eclipse自带的cvs插件非常像。&lt;BR&gt;&lt;STRONG&gt;p4merge：&lt;/STRONG&gt;这是一个文件对比/合并工具，TortoiseSvn自带的文件比较工具还比较好用，但合并工具就差点了，起码没有vss的好用。能找到的工具大多只支持双视图合并。p4merge则能支持三视图甚至四视图，用起来方便很多。开发这个工具的公司也是做配置管理软件的，不过这个公司比较好，只对它的服务器端收费，其他工具都可以免费用。&lt;BR&gt;&lt;A href="http://www.tigris.org/"&gt;&lt;STRONG&gt;http://www.tigris.org/&lt;/STRONG&gt;&lt;/A&gt;&lt;STRONG&gt;：&lt;/STRONG&gt;这是subversion、TortoiseSvn、AnkhSvn以及其它众多开源软件开发管理工具的官方站点。&lt;BR&gt;&lt;A href="http://www.subversion.org.cn/"&gt;&lt;STRONG&gt;http://www.subversion.org.cn/&lt;/STRONG&gt;&lt;/A&gt;&lt;STRONG&gt;：&lt;/STRONG&gt;svn中文网站。&lt;BR&gt;&lt;A href="http://www.iusesvn.com/"&gt;&lt;STRONG&gt;http://www.iusesvn.com/&lt;/STRONG&gt;&lt;/A&gt;&lt;STRONG&gt;：&lt;/STRONG&gt;也是一个svn的中文网站，内容和上一个有很多是重复的。&lt;BR&gt;&lt;/DIV&gt;
&lt;DIV&gt;&lt;/DIV&gt;&lt;img src ="http://blog.vckbase.com/localvar/aggbug/31344.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>局部变量</dc:creator><title>锁？不锁？如何锁？</title><link>http://blog.vckbase.com/localvar/archive/2007/10/15/29995.html</link><pubDate>Mon, 15 Oct 2007 01:46:00 GMT</pubDate><guid>http://blog.vckbase.com/localvar/archive/2007/10/15/29995.html</guid><wfw:comment>http://blog.vckbase.com/localvar/comments/29995.html</wfw:comment><comments>http://blog.vckbase.com/localvar/archive/2007/10/15/29995.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://blog.vckbase.com/localvar/comments/commentRss/29995.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/localvar/services/trackbacks/29995.html</trackback:ping><description>&lt;DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 加锁、解锁(同步/互斥)是多线程中非常基本的操作，但我却看到不少的代码对它们处理的很不好。简单说来有三类问题，一是加锁范围太大，虽然避免了逻辑错误，但锁了不该锁的东西，难免降低程序的效率；二是该锁的不锁，导致各种莫名其妙的错误；三是加锁方式不合适，该用临界区的用内核对象等，也会降低程序的效率。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 要正确的运用锁操作，首先要弄清楚什么时候需要加锁。很多书上都说在可能&amp;#8220;同时发生多个写操作&amp;#8221;或&amp;#8220;同时发生读写操作&amp;#8221;时，应该加锁。这固然没什么错，但我认为它没有说到问题的根上，更准确的表述应该是：如果不加锁会导致不可容忍的数据不一致，那么就应该加锁。据此，我在下表中列出了多线程中应该加锁和无需加锁的条件，其中的&amp;#8220;简单数据类型&amp;#8221;是指cpu可以在一条指令中完成操作的数据类型，一般整形和所有比整形小的数据类型都是，除此之外的类型都属于&amp;#8220;复杂数据类型&amp;#8221;，例如你自己定义的结构体等。&lt;BR&gt;
&lt;TABLE style="WIDTH: 590px; HEIGHT: 87px" cellSpacing=1 cellPadding=1 width=590 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;操作的结果与初值无关&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;操作的结果与初值相关&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;写简单数据类型&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;不需要加锁①&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;需要加锁②&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;写复杂数据类型&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;需要加锁③&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;需要加锁④&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;读简单数据类型&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;不需要加锁⑤&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;不需要加锁⑥&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&amp;nbsp;读复杂数据类型&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;需要加锁⑦&lt;/TD&gt;
&lt;TD&gt;&amp;nbsp;需要加锁⑧&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/DIV&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 大家可能注意到，在第1、5、6种情况下，我认为可以不加锁，粗看起来，这与书上的说法有些矛盾。其实却不然，因为这些操作可以在一条指令内完成，所以它们具有天然的&amp;#8220;原子性&amp;#8221;，我们可以认为cpu已经给它们加锁了，我们没必要再画蛇添足。如果这个理由还不够的话，你不妨想一下我们再加一次锁是否有用，看下面的代码(以第1种情况为例)： 
&lt;TABLE style="BORDER-COLLAPSE: collapse" borderColor=#999999 cellSpacing=0 cellPadding=0 width="75%" bgColor=#f1f1f1 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P style="MARGIN: 5px; LINE-HEIGHT: 150%"&gt;&lt;CODE&gt;&lt;SPAN style="COLOR: #000000"&gt;Lock&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff9900"&gt;// ①&lt;BR&gt;&lt;/SPAN&gt;n &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; 10&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff9900"&gt;// ②&lt;BR&gt;&lt;/SPAN&gt;Unlock&lt;SPAN style="COLOR: #0000cc"&gt;(&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;)&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff9900"&gt;// ③&lt;BR&gt;&lt;/SPAN&gt;&lt;SPAN style="COLOR: #0000ff"&gt;int&lt;/SPAN&gt; x &lt;SPAN style="COLOR: #0000cc"&gt;=&lt;/SPAN&gt; n&lt;SPAN style="COLOR: #0000cc"&gt;;&lt;/SPAN&gt; &lt;SPAN style="COLOR: #ff9900"&gt;// ④&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;看出来了吗？不管语句①③是否存在，这段代码执行完毕后，我们都无法保证x的值是10。也许你会想如果把③④两条语句的位置换一下，x就肯定是10了。可是在这个例子中，想让x是10，为什么不把语句④直接换成&amp;#8220;int x = 10;&amp;#8221;呢？既省了加锁，有减少了键盘的磨损，何乐而不为？！而且