<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title><![CDATA[Sandy_    --&gt; Mirror]]></title>
<link>http://sandy-sp.com/blog/</link>
<description><![CDATA[努力 &amp; 梦想]]></description>
<language>zh-cn</language>
<copyright><![CDATA[Copyright 2005 PBlog3 v2.8]]></copyright>
<webMaster><![CDATA[sandy_sp@qq.com(sandy)]]></webMaster>
<generator>PBlog2 v2.4</generator> 
<image>
	<title>Sandy_    --&gt; Mirror</title>
	<url>http://sandy-sp.com/blog/images/logos.gif</url>
	<link>http://sandy-sp.com/blog/</link>
	<description>Sandy_    --&gt; Mirror</description>
</image>

			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=35</link>
			<title><![CDATA[C++ trick之del&#101;te未完成类型（incomplete class types）]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[技术文章]]></category>
			<pubDate>Mon,16 Jan 2012 12:58:43 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=35</guid>
		<description><![CDATA[<strong>C++ trick之del&#101;te未完成类型（incomplete class types）</strong><br/><br/>我们知道，C++的del&#101;te有以下特性：<br/>1.删除一个对象并回收其内存。<br/>2.如果这个对象有析构函数，在回收内存前会先调用其析构函数。<br/>3.del&#101;te 和 del&#101;te[]不能混用。<br/>4.del&#101;te NULL不会发生任何事情。<br/>5.del&#101;te 可以被重载。<br/><br/>今天，再来说下del&#101;te还有一个奇怪的特性，就是它<strong>可以删除“未完成类型（incomplete class types）”的指针指向的目标</strong>。<br/><br/>什么是“未完成类型”呢？<br/><br/>我们知道C++的class或者struct都可以前置声明，比如：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH0" >class B; // #1&nbsp;&nbsp;here B is an incomplete class<br/>class A<br/>{<br/>public:<br/>&nbsp;&nbsp;B* pointer_b;<br/><br/>};<br/><br/>class B // here is the definition of B<br/>{<br/>public:<br/>&nbsp;&nbsp;A* pointer_a;<br/>}; // #2&nbsp;&nbsp;now B is a complete class type<br/></div><script language="JavaScript">CodeHilight("UBBdCodeH0","cpp" );</script><br/>如上，#1处，class B只有前置声明，而并没有任何实体。这时候编译器只知道有一个名叫B的class，但对B具体是什么样一无所知，这时候B就是一个incomplete class type，未完成类型。而等到了#2处，B的定义已经完成，这时候B就是一个完成了的类型了。<br/><br/>很奇怪的是，C++允许你去del&#101;te一个未完成类型的指针。<br/>虽然在删除那些trivial的结构体（老式C风格的、不带任何函数包括构造析构函数的结构体）的时候不会有什么错误，<span style="color:Blue">但这个特性显然会在其它地方导致许多问题：编译器对del&#101;te的目标类型具体情况一无所知，显然就没法调用其析构函数。而更可怕的是，如果目标类型重载了operator new/del&#101;te，但一无所知的编译器依旧按照默认的del&#101;te方式来处理，于是会导致不正确的内存分配/回收，从而导致程序崩溃。</span><br/><br/><br/>对于这种情况，如今现在许多编译器，会给出一个WARNING，提醒“你正在试图del&#101;te一个未完成类型”。<br/>比如G++是这样的：<br/><div class="UBBPanel quotePanel"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="引用内容"/> 引用内容</div><div class="UBBContent">warning: possible problem detected in invocation of del&#101;te operator:<br/>warning: ‘xxx’ has incomplete type<br/>warning: forward declaration of ‘struct XXXX’<br/>note: neither the destructor nor the class-specific operator del&#101;te will be called, even if they are declared when the class is defined.<br/></div></div><br/>不过遗憾的是没法给ERROR（因为标准中定义它是合法的），所以可执行文件依旧会生成。<br/>而且如果你的编译参数中有忽略警告（或者你人本身压根就无视WARNING了），那么这种错误就没法避免了。<br/>更糟糕的是，并不是所有编译器都会给WARNING，对于某些比较偷懒的编译器，你也只好念阿弥陀佛了。。<br/><br/><strong>……不，其实除了念阿弥陀佛之外，还是有一些办法能主动发现这些问题的。</strong><br/> <br/>boost就给了我们一个很好的范例（……为什么又是boost？）<br/>它提供了一个头文件：<span style="color:Blue">checked_del&#101;te.hpp</span><br/>里面定义了四个玩意：<br/>两个函数：checked_del&#101;te()和checked_array_del&#101;te()<br/>还有两个类：checked_del&#101;ter和checked_array_del&#101;ter<br/><br/>看名字就知道啦，这明显就是提供一种可检查出上述问题的del&#101;te操作，帮助那些偷懒的编译器，或者偷懒的你，查出问题所在。<br/><br/>使用方法是显而易见的，checked_del&#101;te(x)代替del&#101;te x，使用checked_array_del&#101;te(x)代替del&#101;te[] x就行了。<br/>那两个类，也很明显，是仿函数。功能与这两个函数完全一致。<br/><br/>使用checked_del&#101;te之后，假如你试图删除一个未完成类型的对象，会直接报编译错误。不过编译错误显然不会是 “你正试图删除未完成类型” 这么直白……事实上会是一堆看似无关的错误输出，比如像这样：<br/><div class="UBBPanel quotePanel"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="引用内容"/> 引用内容</div><div class="UBBContent">In function ‘void boost::checked_del&#101;te(T*) [with T = XXXX]’:<br/> instantiated from here<br/> error: invalid application of ‘sizeof’ to incomplete type ‘XXXX’ <br/> error: creating array with negative size (‘-0x00000000000000001’)<br/> error: invalid application of ‘sizeof’ to incomplete type ‘XXXX’ <br/> error: creating array with negative size (‘-0x00000000000000001’)<br/></div></div><br/>因为C++98/03标准还不支持自定义编译错误（最新的C++11标准则已经<a target="_blank" href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1720.html" rel="external">支持</a>），所以这是通过一种特殊的技巧来做到引发编译错误（后文会提到），因此实际上的编译错误看起来完全不相干。但好歹、至少、最起码，它报错了，不会把错误漏掉。<br/> <br/><br/>另外，它的检查完全是编译期的行为，不耗费任何运行时的资源或时间。在运行时的效率和del&#101;te、del&#101;te[]完全没有区别。<br/><br/>可能有人好奇，它内部是怎么实现的？<br/>其实很简单（虽然用到了一点小技巧）：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH1" >template&lt;class T&gt; <br/>inline void checked_del&#101;te(T * x)<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;// intentionally complex - simplification causes regressions<br/>&nbsp;&nbsp;&nbsp;&nbsp;typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]; // #1<br/>&nbsp;&nbsp;&nbsp;&nbsp;(void) sizeof(type_must_be_complete);&nbsp;&nbsp;// #2<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te x;<br/>}<br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH1","cpp" );</script><br/><br/>就这么简短。全部的奥妙就只在#1和#2两句话处。<br/>你看到了sizeof，是的，又是sizeof，基本上说你在模版编程的地方会大量的见到sizeof，它的一些特性可以搞出许多技巧出来，以至于有人认为“sizeof技术”是模版元编程的最重要技术。<br/>sizeof最重要的特性之一是，它完全是编译期间计算的，运行时它就已经是计算好的结果了。<br/>经常可以看到有些公司的笔试题里面有问：int i = 5; int j = sizeof(i++); 执行完了i是多少？因为sizeof是编译期的行为，所以里面的 i++ 根本不会在运行时被执行，所以 i 依旧是5。<br/><br/>扯远了，回头看代码。原来如此，通过sizeof运算符来判断一个类型是否是未完成类型。如果一个class是未完成的，那么sizeof的结果是0，否则是其实际长度。<br/>然后在函数里面耍了个技巧，定义了一个数组类型，可是数组的长度根据sizeof结果来判别：0的话是-1否则是1。我们知道长度为1的数组是合法的，长度为-1的数组是非法的，所以通过定义一个长度为-1的数组就可以人为引发一个编译错误。<br/>#2处，并没有直接定义这样一个数组，因为定义数组也是要耗费运行时的资源的，而这里再次使用了sizeof，强制求这个数组的大小。编译器要去求这个数组的大小，就得去看这个数组的定义，然后发现它的长度是-1，就会报错。这样既可以在编译期人为引发错误，又完全不耗费运行时的资源和时间。<br/><br/>经过了#1和#2两句检查之后，如果还没有引发编译错误的，那肯定已经是“完成了的类型”了，直接del&#101;te之即可。<br/><br/>这就是checked_del&#101;te的全部了。checked_array_del&#101;te类似，只是最后调用的是del&#101;te[]操作符而已。<br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=33</link>
			<title><![CDATA[linux下的多线程与多进程混用问题]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[技术文章]]></category>
			<pubDate>Sun,11 Dec 2011 19:20:13 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=33</guid>
		<description><![CDATA[<strong>linux下的多线程与多进程混用问题</strong><br/><br/>对于并发任务，windows和unix、类unix系统有着比较不同的倾向性。<br/><br/>unix和类unix的系统，由于fork的存在，非常容易开发多进程程序。而相比之下，windows提供的Cr&#101;ateProcess则很不适合开发多进程的程序。<br/>相反，windows一开始就提供了对线程这个概念的完善支持，而unix的早期则没有线程这一概念，后期才加入。<br/><br/>所以通常情况下，在windows下要做并发任务，人们首先想到的是多线程；unix下，人们或许首先想到的是多进程。而后期的各种unix分支或者linux，人们则会犹豫一下，到底是多线程呢，还是多进程呢？<br/><br/><br/>以上只是临时唠叨，这次真正想说的是，多线程和多进程混用。因为多线程和多进程各有各的好处，所以难免会出现，一开始写着多线程程序，后来就想再加入多进程用用，或者反之。<br/><br/>可惜答案是比较残酷的：<span style="color:Red">能不混用，就尽量不要混用。</span><br/><br/>在linux下多线程和多进程混用，很容易出现各种问题，<strong>坑非常多</strong>。<br/><br/>要说原因，其实很简单，就是源于在多线程fork下的一个行为：<br/><strong>多线程程序，一旦fork，那么产生的子进程，只会保留有一个线程，即调用fork的线程。其它的线程，瞬间蒸发。</strong><br/><br/>粗看貌似没啥大问题，但是如果对并发任务的原子性比较敏感的同学已经可以看出来了：这个“瞬间蒸发”是个大问题。<span style="color:Red">许多线程可能正在做一系列需要保证原子性的操作，但是由于线程瞬间蒸发，所以这个原子性被破坏了。</span><br/>举个最简单的例子：假如某个线程正在从A的账户上转账50元给另一个人B。它先把A的账户减去50元然后再给B的账户加上50元。这本应是一个原子操作，要么全部成功，要么全部失败，不应该存在成功一半的中间状态。而由于上面所说的“瞬间蒸发”的影响，假设这个线程在刚刚给A减去50元之后，它被“蒸发”掉了，那么这50元就永远的丢失了，B永远也得不到这笔转账。<br/><br/>类似的这种需要保证原子性的操作非常多，而你一旦fork，谁也不能保证其它线程在蒸发后会留下什么样一个烂摊子给你。你需要面对的是一个数据一致性有很大问题的内存。<br/><br/><br/>这还只是在多线程下fork的一个问题的简单层面而已。<br/>稍微从这里往深推断下，就会发现还有更严重的：锁。<br/><br/><span style="color:Red">假设其它线程给某些资源加了锁，然后正在进行某些操作，之后突然这个线程蒸发了。great，这个锁这下没人能解开了，成了名副其实的死锁。</span><br/>这时候如果你试图访问这个临界区，则很有可能会被卡死在这里。<br/><br/>有些人可能觉得，以上这些情况可以通过谨慎合理的一些程序行为来绕过。比如在fork前，先保证把所有的锁都解开，之后再fork。<br/><br/>好吧，或许你的代码可以那么做，但再看看下面的问题：<br/><span style="color:Red">许多系统函数调用，是用了锁的。</span><br/><br/>比如最简单的printf。在多个线程往stdout输出的时候，是使用锁来解决冲突问题的。假设一个线程在调用printf的时候突然蒸发，那么有可能，之后任何使用printf的地方都会卡死掉。<br/><br/>你可以约束你的代码，但你没法约束系统函数。除非你一个系统函数都不用，而这显然不太可能。<br/>更要命的是，大量的系统函数，都有这样的问题。<br/><br/>我们把在多线程环境下fork之后还能不受影响的函数，称之为fork-safe的，反之是fork-unsafe的。<br/>那么，目前多数系统调用，都是fork-unsafe的。<br/><br/>而且更加郁闷的是，你不清楚具体哪些系统调用是fork-safe的哪些是fork-unsafe的，这些在POSIX规范中并没有规定，所以你根本查不到一个通用的标准说法。——只有一个例外，exec 函数，可以保证是fork-safe的，具体原因自己想想就明白了。<br/><br/>所以，既然你没法回避使用系统函数，那么你就不能保证你的各个线程在fork的时候是安全的。再加上原子操作和数据一致性的各种问题，坑是非常多的。<span style="color:Blue">因此，除非fork以后马上exec，否则，多线程环境下，别用fork。</span><br/><br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=30</link>
			<title><![CDATA[.Net下访问https]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[技术文章]]></category>
			<pubDate>Tue,13 Sep 2011 15:02:42 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=30</guid>
		<description><![CDATA[在.net 下，想要通过http来GET或POST访问一个目标，很容易，使用HttpWebRequest对象即可。<br/><br/>但如果要访问的目标是https，那么就涉及到SSL问题，有时候会出点小问题。比如报错：<span style="color:#aa00aa">“根据验证过程，远程证书无效。”</span>，catch一下发现是个WebException： <span style="color:#aa00aa">“基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系。” </span><br/><br/>错误提示已经很清楚，就是因为证书无效，导致无法建立SSL信任关系。<br/>这可能是对方使用了无效的证书，或者是本地不信任这个证书的签发机构。<br/><br/>这种情况其实很普遍，因为并不是任何时候人们都有功夫去一个机构签发一个证书。<br/>有些时候，我们只是要确保通讯的过程是加密的即可，至于对方的证书是否是由权威签发，我们可以不用关心（比如两个小公司之间的一些项目，使用https通讯，为了这个专门去一个权威机构签发一个通用证书太麻烦，在双方事先可以沟通的基础上，完全可以自己给自己颁发一个证书来用）。<br/>再有，比如一些测试环境，我们只是测试系统是否可用，证书验证失败是很常有的事情。<br/><br/>对于这种情况下，我们程序的处理过程就不能采用.Net默认的规则去处理了，我们必须自定义证书验证规则。<br/>大概看了下，网上大多数给出的是这么一个解决方案：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C# 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH0" >private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;<br/>}<br/><br/>//...<br/><br/>ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);<br/></div><script language="JavaScript">CodeHilight("UBBdCodeH0","csharp" );</script><br/>通过写一个委托，来自定义证书的验证过程，这里直接return true，表明总是信任。或者类似的.net 1.1下的实现ICertificatePolicy 接口的方案。<br/><br/><span style="color:#aa00aa">这些方案的确可以让https访问成功，但是本质上是信任了所有的证书，相当于忽略了证书验证这一步，用于测试环境或许还可以，但真正线上服务使用这种方式会很危险：随便一个假冒钓鱼网站搞一个虚假证书就可以骗得你的信任。</span><br/>这样可不好，我们还是需要一个手段来证明下这个证书是否是可信的。<br/><br/>其实，假如事先可以获得对方的证书，那么我们就可以通过证书比较来确定这个证书是否可信了。<br/><br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C# 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH1" >List&lt;X509Certificates&gt; trustCerts;<br/><br/>public static void LoadTrustCerts(string certsPath)<br/>{<br/>&nbsp;&nbsp;trustCerts = new List&lt;X509Certificates&gt;();<br/>&nbsp;&nbsp;string[] strFiles = IO.Directory.GetFiles(certPath);<br/>&nbsp;&nbsp;foreach(string strFile in strFiles)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (strFile.Length &lt; 4 || strFile.Substring(strFile.Length - 4) != &#34;.cer&#34; ) { continue;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;trustCerts.Add(X509Certificates.X509Certificate.Cr&#101;ateFromCertFile(strFile));<br/>&nbsp;&nbsp;}<br/>}<br/><br/>public static bool CheckSSLCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)<br/>{<br/>&nbsp;&nbsp;foreach(X509Certificate cert in trustCerts)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if cert.Equals(certificate)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;}<br/>&nbsp;&nbsp;return false;<br/>}<br/><br/>ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);<br/></div><script language="JavaScript">CodeHilight("UBBdCodeH1","csharp" );</script><br/>先使用LoadTrustCerts将本地可信的那些证书载入，然后将CheckSSLCert作为自定义证书的验证过程。<br/>这样，只要本地保存的证书没有问题，整个https通讯也就是安全的。<br/><br/>当然，这种做法看起来有点sb，证书的意义（验证并协商密钥）已经失去很多：既然事先可以沟通获取证书，完全可以事先协商好一个对称加密密钥，然后使用普通的http来通讯。但是实际上在具体项目中，有时候处于各种原因，人们还是会选择https的SSL通道，这样的话在本地通过对比来验证证书也算是一个实际而安全的解决方案了。<br/><br/><br/>因为.net 可以通过为ServicePointManager.ServerCertificateValidationCallback指定自定义的委托，其实可以制作多种验证策略，然后通过配置来决定使用哪一个。本人目前在项目中有用三种策略：默认（使用信任机构去验证证书）、本地匹配（就是上面的证书比对）、忽略（信任所有证书）。在测试环境下，使用后两种策略，在线上环境使用前两种策略。基本上可以满足各种需求。<br/><br/>-----------------<br/><br/>上面说了一堆SSL证书验证的问题，可能一些不了解SSL相关知识的人会有点摸不着头脑。<br/>所以最后附上点东西，可以简单了解下SSL安全通讯到底是什么一个过程，最后再回头梳理就容易很多：<br/><br/>首先，要安全通讯，显然得加密，但是假如与一个不认识的目标通讯，加密的密钥如何确定呢？其实这里就用到了公钥加密和证书，使用<a target="_blank" href="http://baike.baidu.com/view/4402174.htm" rel="external">公钥算法</a>来协商一个密钥，然后之后使用普通对称加密来完成后续的安全通信。<br/><br/>下面是对这一过程的形象的比喻（摘自<a href="http://blog.chinaunix.net/u2/82806/showart_1341720.html" target="_blank" rel="external">http://blog.chinaunix.net/u2/82806/showart_1341720.html</a>）：<br/><div class="UBBPanel quotePanel"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="引用内容"/> 引用内容</div><div class="UBBContent"><br/>假设A与B通信，A是SSL客户端，B是SSL服务器端，加密后的消息放在方括号[]里，以突出明文消息的区别。双方的处理动作的说明用圆括号（）括起。<br/><br/>A：我想和你安全的通话，我这里的对称加密算法有DES,RC5,密钥交换算法有RSA和DH，摘要算法有MD5和SHA。<br/><br/>B：我们用DES－RSA－SHA这对组合好了。<br/><br/>这是我的证书，里面有我的名字和公钥，你拿去验证一下我的身份（把证书发给A）。<br/><br/>A：（查看证书上B的名字是否无误，并通过手头早已有的数字的证书验证了B的证书的真实性，如果其中一项有误，发出警告并断开连接，这一步保证了B的公钥的真实性）<br/><br/>（产生一份秘密消息，这份秘密消息处理后将用作对称加密密钥，加密初始化向量和hmac的密钥。将这份秘密消息-协议中称为per_master_secret-用B的公钥加密，封装成称作ClientKeyExchange的消息。由于用了B的公钥，保证了第三方无法窃听）<br/><br/>我生成了一份秘密消息，并用你的公钥加密了，给你（把ClientKeyExchange发给B）<br/><br/>注意，下面我就要用加密的办法给你发消息了！<br/><br/>（将秘密消息进行处理，生成加密密钥，加密初始化向量和hmac的密钥）<br/><br/>[我说完了]<br/><br/>B：（用自己的私钥将ClientKeyExchange中的秘密消息解密出来，然后将秘密消息进行处理，生成加密密钥，加密初始化向量和hmac的密钥，这时双方已经安全的协商出一套加密办法了）<br/><br/>注意，我也要开始用加密的办法给你发消息了！<br/><br/>[我说完了]<br/><br/>A: [我的秘密是...]<br/><br/>B: [其它人不会听到的...]<br/><br/>从上面的过程可以看到，SSL协议是如何用非对称密码算法来协商密钥，并使用密钥加密明文并传输的。还有以下几点补充：<br/><br/>1.B使用数字证书把自己的公钥和其他信息包装起来发送A，A验证B的身份，下面会谈到A是如何验证的。<br/><br/>2.A生成了了加密密钥、加密初始化向量和hmac密钥是双方用来将明文摘要和加密的。加密初始化向量和hmac密钥首先被用来对明文摘要（防止明文被篡改），然后这个摘要和明文放在一起用加密密钥加密后传输。<br/><br/>3.由于只有B有私钥，所以只有B可以解密ClientKeyExchange消息，并获得之后的通信密钥。<br/><br/>4.事实上，上述过程B没有验证A的身份，如果需要的话，SSL也是支持的，此时A也需要提供自己的证书，这里就不展开了。在设置IIS的SSL Require的时候，通常默认都是igore client certification的。<br/><br/></div></div><br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=29</link>
			<title><![CDATA[代码示例：Win下简单进程间同步]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[技术文章]]></category>
			<pubDate>Fri,24 Jun 2011 17:12:01 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=29</guid>
		<description><![CDATA[<span style="color:#ffffcc">该例子运行后会先检查相同进程的数量，若进程已达到8个则直接退出，未达到8个则显示进程数量，输入e退出，输入其它则刷新显示进程数量。</span><br/><br/>主要演示以下几点：<br/>1、使用FileMapping来实现进程间内存共享。<br/>2、使用Interlocked API来实现无锁状态下的进程间访问。<br/><br/>在Visual C++ 2008和minGW 5.1.6(GCC 3.4.5)下编译测试通过。<br/><br/><ul><br/><li>运行一个试试，注意看上面显示的进程数量。<br/></li><li>不要退出，再启动一个试试，看看进程数量的变化。可以切回第一个进程然后回车刷新看看。<br/></li><li>多试几次，看看最多能创建几个进程。<br/></li><li>输入e退出几个进程再看看。（不要Ctrl+C中断或者直接叉掉）<br/></li></ul><br/><br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH0" >#include &lt;windows.h&gt;<br/>#include &lt;tchar.h&gt;<br/><br/>#include &lt;stdio.h&gt;<br/><br/>const size_t SHAREDMEMSIZE = 1024*1024;<br/><br/>const LPCTSTR SHAREDMEMNAME = _T(&#34;testProcessSyncMem&#34;);<br/><br/>HANDLE hMapping = 0;<br/><br/>void* pSharedMemAddress = NULL;<br/><br/>const int maxInstCount = 8;<br/><br/><br/>// 共享内存区的数据结构，所有需要共享的变量定义在这个结构里面 <br/>struct share_data_t<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;volatile long numInst; //不要忘了volatile修饰 <br/>};<br/><br/>// 这个宏仅仅是为了兼容老版本的Interlocked API接口定义<br/>// (老版本的Interlocked API 将参数声明为LPLONG而不是 LONG volatile*，需要cast) <br/>#define V(x) ((LPLONG)(x)) <br/><br/>// 初始化共享内存区，若已有此区则直接获取其地址，否则创建此区 <br/>int initSharedMemory()<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;// 创建文件内存映射，第一个参数传0表示创建一个空的文件映射，若同名映射存在则直接返回句柄 <br/>&nbsp;&nbsp;&nbsp;&nbsp;hMapping = ::Cr&#101;ateFileMapping(0, NULL, PAGE_READWRITE, 0, SHAREDMEMSIZE, SHAREDMEMNAME);<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (!hMapping)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tprintf(_T(&#34;failed to cr&#101;ate o&#114; get file mapping![%d]\n&#34;), GetLastError());<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;// 将内存映射的一部分(本例是全部)映射到当前进程的地址上 <br/>&nbsp;&nbsp;&nbsp;&nbsp;pSharedMemAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE , 0, 0, SHAREDMEMSIZE );<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (!pSharedMemAddress)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tprintf(_T(&#34;failed to get mapping address![%d]\n&#34;), GetLastError());<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hMapping);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br/>}<br/><br/>// 释放共享内存区 <br/>int releaseSharedMemory()<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if (pSharedMemAddress)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UnmapViewOfFile(pSharedMemAddress);<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if (hMapping)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 只需关闭句柄即可，当所有句柄都关闭后会自动销毁共享内存区 <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CloseHandle(hMapping);<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br/>}<br/><br/><br/>int main(int argc, _TCHAR* argv[])<br/>{<br/>&nbsp;&nbsp;&nbsp;&nbsp;if( 0 &gt; initSharedMemory())<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;volatile share_data_t* pdata = reinterpret_cast&lt;share_data_t*&gt;(pSharedMemAddress);<br/>&nbsp;&nbsp;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;//使用Interlocked API来实现原子（atomic）自增操作，返回自增前的值 <br/>&nbsp;&nbsp;&nbsp;&nbsp;int num = InterlockedIncrement(V(&amp;pdata-&gt;numInst)); <br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;if(num &gt; maxInstCount)<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tprintf(_T(&#34;the process count reached max, programe terminated&#34;));<br/>&nbsp;&nbsp;&nbsp;&nbsp;}else<br/>&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tprintf(_T(&#34;current process instance count:%d&#34;), num);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int input = 0;<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (&#39;e&#39; != getchar())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_tprintf(_T(&#34;current process instance count:%d&#34;), pdata-&gt;numInst);<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br/>&nbsp;&nbsp;&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;InterlockedDecrement(V(&amp;pdata-&gt;numInst)); //原子(atomic)自减 <br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;releaseSharedMemory();<br/>&nbsp;&nbsp;&nbsp;&nbsp;return 0;<br/>}<br/><br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH0","cpp" );</script><br/><br/>使用的共享方式是FileMapping，文件映射。虽然名称中有“文件”二字，但是实际上你完全可以不指定文件，而直接在内存中创建一块数据区用于进程间的共享。你可以自定义一块struct，将其对应到这块地址上来方便访问。但请注意，任何需要堆(heap)上分配空间的变量无法在此处共享，除非你自己写一个内存分配器。<br/><br/>而同步方式则是由Interlocked API提供的Lock Free技术，避开了加锁也能保证正确的进程间访问。<br/>InterlockedIncrement提供一个原子自增操作，返回值是自增之前的数值。它可以保证整个过程是原子（atomic）的（即不可被打断，期间不会有其它东西修改操作数）。它使用CPU指令级别的手段，避免了加读写锁的开销。InterlockedDecrement提供原子自减操作，与之类似。<br/><br/>（未完待续）]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=28</link>
			<title><![CDATA[折腾电脑记.2]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[日常记录]]></category>
			<pubDate>Fri,24 Jun 2011 16:52:52 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=28</guid>
		<description><![CDATA[接着<a target="_blank" href="http://sandy-sp.com/blog/article.asp?id=25" rel="external">之前所说的</a>，电源不淡定了。再加上在CHH混时间略长，各种毒害的结果，所以就继续了折腾的过程：<br/><br/><strong>0x10，换电源。</strong><br/>TT XP420，带着5850 + 开四核开L3的440，实在觉得不保险，而且眼看快到夏天，担心功率缩水，于是物色各种电源。之前没被毒害的时候，电源这种玩意，咱向来是不关心的。毕竟这玩意好坏几乎无法体现在性能上，所以本着“节约”、“性价比”的精神，向来都是买额定瓦数足够、品牌不错的即可。<br/><br/>可是呢，自从在DIY这里陷深了之后，逐渐的开始觉得这样不行了。好歹电源关系着整个系统的稳定性啊，而且对于想玩超频的人来说，电源怎么的也得高品质吧。于是开始研究这个之前没怎么了解的领域了。开始去了解什么是单路12V什么是双路12V，各路输出如何分配，什么是转换效率和功率因数，什么是波纹，什么是动态特性，什么是冷负载什么是热负载什么是交叉负载，什么是全模什么是半模……<br/><br/><div class="UBBPanel quotePanel"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="引用内容"/> 引用内容</div><div class="UBBContent">在这里也才明白了，看电源，其实只要看一个东西就行了：功率价格比。越高的电源越烂，越低的越好。<br/>那些只卖一两百元却动辄400~500W额定功率的，几乎可以肯定品质稀烂。<br/>电源都喜欢玩虚标，过去有商家喜欢用峰值功率来唬人，现在商家喜欢用额定功率来唬人。额定功率也是有水分的啊，简单的来说，这个额定功率在不同工作温度下测试结果会相差很大，在25度下额定400W的电源，可能在40度下就只有额定300W了，这就是被商家钻空子的地方啊。想想你平时机箱内部的散热情况，基本可以肯定电源工作在40度以上，那么你功率缩水了多少呢？所以给菜鸟们常常的一个忠告就是：如果你不懂电源，那额定功率就要选超过需求25%以上。需要300W就买400W，留够余量，以免到时候缩水让自己头疼。</div></div><br/><br/>好了废话了那么多，经过了乱七八糟的纠结，最终入了这个：<br/><img src="http://sandy-sp.com/blog/attachments/month_1110/v20111024203330.png" border="0" alt=""/><br/><strong>ANTEC Neo ECO 450</strong><br/>为什么选这个？就看中了海韵代工，S12II方案。虽相比原版S12II用料有所缩水，但也相当不错。所有线材都是18AWG的。<br/><br/><span style="color:Brown">+12V 实续输出34A 共408W<br/>+5V、+3.3V 实续输出各20A 供120W<br/>共计额定450W、最大550W功率。 <br/>通过80plus白牌，不过在220V电压下实际上已经超过了80plus铜牌标准。</span><br/><br/>不过唯一不爽的就是没有附赠模组线，对于我双6pin的HD5850来说，一个单6pin的PCI-E电源接口显然不够，害得我又花了25元在tb上买了根6+2pin模组线。<br/><br/>入了这个之后，终于心里安稳了。而且超频方面的确有实际效果，现在全开的440超到3.6G已经比较稳定了。显卡OC到850。<br/><br/>电源在某东上买的，￥429。其实TB上有不少￥399的，不过后来查了下ANTEC在大陆的售后策略，还是老老实实的上某东买了。ANTEC是店保啊，在谁家买找谁送修，要是TB上面可就麻烦了。<br/><br/>&nbsp;&nbsp;<br/> <br/><br/><strong>0x0a，加风扇。</strong><br/><br/>Neo Eco 450电源号称环保静音，说是相当安静的一款电源。可是在我这里却闹了个小情况：刚装上之后，发现整个机箱噪音大了不少，可以明显的听到风扇的噪音，换回老的XP420就安静了。当时一下子懵了，怎么静音电源反倒更吵？<br/><br/>后来仔细查看温度监控，发现CPU的温度上升了非常多，导致CPU风扇转的相当快，噪音主要就是因为这个产生的。另外显卡的风扇也从原来的21%转速提高到了29%。看来整个机箱内的温度都升高了不少。为啥捏？后来经过自己分析以及网上查证，明白了：<br/>Neo ECO 系列电源，的确是静音电源，风扇转得很慢，因此很安静。可是原来机箱内部的空气是靠电源来抽到外面的，现在电源风扇转得慢，它自己倒安静了，可却导致了整个机箱内部抽风不足，热量堆积，让CPU和显卡风扇吵闹不已。囧，哭笑不得。<br/><br/>不过解决问题的方法倒也简单。这本来其实不该怪电源的，是机箱内部风道本来就有缺陷。我的酷妈毁灭者机箱前后各有一个风扇位，可是机箱只原配了前面的进风风扇，后面的出风风扇位置却是空的。因此只要买一个12cm风扇装在这个位置往外面抽风就ok啦。<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1110/m2011102420574.png" border="0" alt=""/><br/>俺还没有蛋疼到连扇子都要精挑细选买高档的地步，于是某东上面选了个超频三的F122了事。话说某东上的价格还真是贵啊，比TB贵了一倍，坑爹无极限啊。<br/><br/> <br/> <br/><br/><strong>0x0b，入鼠标，开始进入标准败家流程：折腾外设。</strong><br/>起因是因为wifi干扰，搞的我之前的双飞燕G7630无线鼠标老跳帧，搞得很不爽。于是把公司zrx童鞋的有线鼠标拿来用。随着zrx童鞋从学校返回公司，马上要面临无鼠可用的境地。于是就要买个有线鼠。同时下决心既然这次要买就买个好的。因为之前的鼠标，在PS的时候，精细微调总会让人觉得很不顺心，所以这次要买个足够精确、既可以大范围移动（我是1080p的桌面），也可以小范围精细移动、工作游戏两不误的鼠标。<br/><br/>最后在razer炼狱蝰蛇、罗技MX518和微软IE3之间纠结半天之后，选择了MX518。<br/><img src="http://sandy-sp.com/blog/attachments/month_1110/u20111024211342.png" border="0" alt=""/><br/>经典鼠标了，没什么说的。第二版略升级了光学引擎，最高到1800dpi，脚贴变大，别的没啥变化。<br/>这个鼠标的三段式dpi条件很好用。配我的1920*1080分辨率很有用，我通过驱动设定为600、1000、1600dpi这三段，根据不同需求切换，很方便。<br/><br/>唯一不爽的是，这货相对于我的手还是偏大了。原来听说它略大，但是没想到还真是巨大，几乎比之前那个G7630大了一倍。手握着都有点握不全。不过还好，时间长了慢慢适应了。<br/><br/> <br/> <br/><br/><strong>0x0c，换2.0音箱。</strong><br/><br/>这个倒不是故意换的，之前的漫步者C2个人觉得还是不错的，不过因为不便于运输，在帝都卖给felicia了。<br/>于是相当长的一段时间里面，我在用一个AMD OEM的小便携式音箱来凑数。基本上也就是到只能发声的级别了，毫无效果可言，无论是玩游戏还是看电影都极其挫。想想还是不能这么凑合下去，毕竟还是得有个桌面级音箱的。于是上tb买之。<br/><br/>本来还想买之前的漫步者C2的，特别是那个独立功放，给本人留下了很好很深的印象。可是丫的一看，tb大涨价，原来不到400的C2现在最便宜的也要480了，郁闷啊。<br/>于是重新搜索之，想想2.0的中频表现更好，个人更重视中频，于是决定搞2.0音箱。最终在几个候选里面圈定了惠威（贵啊）下面的d1010 MKII和d1080 MkII，后者虽然经典音箱，可是还是明显超预算了，而且那大小估计我桌子也够呛。于是选前者。。<br/><br/>在最终在某东下单前，却发现新上市了款d1010-IV，据说是d1010系列的终极改进版，简单看了下评测，貌似不错，再加上蛋疼的某东d1010 mkII缺货，于是就￥460入了d1010-IV。<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1110/o2011102513010.png" border="0" alt=""/><br/><br/>入了之后煲箱两天，音乐一天噪音一天，然后大概听了下，果然不错。不愧是2.0系统，中频比之前C2好了不少。而且令我吃惊的是，低音也很强劲，完全不比那帮带五寸低音炮的2.1系统弱。看来传说中的d1010-IV对低音有所改进是真的。<br/><br/>总之音质对俺这种普通人来说已经很出色了，不过惠威的易用性就做得差多了。除了传统的三个音量调节旋钮，别的啥都没有。那在侧面的旋钮让人调整很不方便，而且也不便于观察当前音箱的状态。在这方面就又怀念起漫步者C2了。独立功放的设计在使用方便程度上真不是一个级别的。全自动的电子调控旋钮（又大又好用），液晶屏，双路音频输入选择，还带遥控器，哪一个都比惠威简陋的三个旋钮强多了。<br/><br/>于是开始先试着装了几个键盘hotkey程序，然后自己写了个setvol程序用于调整系统音量，但是在玩游戏的时候，依旧不爽。最后忍无可忍，上线控。<br/><br/>先看了下惠威原厂的线控，就一个旋钮用于调节音量，除此之外啥功能都没有，丫的还要￥90多，一边去吧。<br/>后来看到了奋达为其A511音箱原配的AX700线控，在tb上单卖只要￥20多，不仅可以调节音量，还带耳机插口，另外还带第二路音频输入，真是功能全面啊，性价比超高。于是果断入了个。<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1110/d20111025131551.png" border="0" alt=""/><br/><br/>买回来后确认了这真是个好货，做工也十分出色，沉甸甸的很厚实。我甚至觉得它的线材比惠威D1010音箱原配的线材都要好。用上了这个之后，感觉已经能部分弥补没有C2的独立功放造成的易用性问题了。<br/><br/><br/><br/><strong>0x0d，入键盘。</strong><br/><br/>自从把8115丢在帝都之后，这阵子一直使用的是BenQ的A800海贝。海贝也是一代经典键盘了，使用了所谓的X架构，打字的手感，个人感觉是薄膜键盘中最好的，较短的键程却能达到很好的段落感和弹性，特有的力度变化，让人觉得清脆无比。<br/><br/>可惜呢，海贝写代码极爽无比，但游戏却渣到不行，原因十分简单：键位冲突。这丫的一开始就是为办公打字等需求设计的，压根没考虑过游戏玩家的需求，一些最基本最常见的按键组合也会冲突。<br/><br/>别的也就忍了，可是，居然按 ←↑ 的同时不能按空格键！一堆飞行游戏给废了，没法开炮。极品飞车也废了，没法漂移。后来发现←↑ 和Z也冲突，好吧，zun你好zun再见。格斗游戏就更不用说了，一堆技能释放不出来。<br/><br/>话说我可不想做单纯的程序猿啊，好歹有点生活有点兴趣爱好是不？游戏不让我打算什么事儿？好歹HD5850的显卡都上了啊。忍，忍，直到th13发布了，兴冲冲的去玩。MLGB的，忘了按键冲突，结果各种撞弹miss啊有木有！<br/><br/>怒了，不忍了，换键盘。MMD，这次老子干脆就来狠点，直接换个全无冲的，看你丫的还冲突！<br/><br/>理论上说，薄膜键盘是几乎不可能做到全无冲的。最多只能做到n键不冲突，或者游戏常用的某些键不冲突。本着一次到尾彻底杜绝杯具的原则，pass。剩下就只有机械键盘和静电容键盘了。丫的都是米物啊，之前想都没想过要买的。这次咬牙上吧！<br/><br/>鉴于静电容太遥远了，太贵族了不考虑。<br/>下来就是机械键盘。<br/><div class="UBBPanel quotePanel"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="引用内容"/> 引用内容</div><div class="UBBContent">机械键盘采用的基本都是德国cherry MX机械轴，常见的轴有五种：青轴、黑轴、茶轴、白轴，还有近来出现的红轴。前四个也被称为cherry的春夏秋冬，手感各不相同，青轴最清脆，像打字机一样感觉最爽，可是不太适合游戏，而且声音较大，比较吵。黑轴反应迅捷，力道十足，回弹迅速，非常适合游戏，但是打字的话时间长了会累。茶轴比较中庸，介于青轴和黑轴之间，特点不太明显，但是最全能，打字游戏样样好。白轴力度最大，太硬，用的人少。至于后来的红轴可以看作是轻黑，手感类似于减轻压力的黑轴。<br/>既然身为游戏程序员，要敲代码+打游戏，选择也就明了，茶。</div></div><br/><br/>然后进入一波三折的选择阶段。<br/>首先，cherry原厂和flico这种贵族牌子显然被pass了。<br/><br/>然后，一开始看上的ducky 1008，一方面ducky这个牌子还是不错的，虽比flico差但比PLU之类的好。另外很重要的一点是看重他的茶青混轴，这个非常有特色，其它牌子都没的。左侧区域87键是茶轴，右侧小键盘是青轴，同一个键盘就可以体验两种手感，对于我等没钱烧外设的人来说是极具诱惑的啊。<br/>可是捏，先不说有个卡榫容易断裂的bug，更重点的是，丫的根本买不到啊！tb上问了x+y家都是没货，等了一个月，还是没货，彻底晕了，换。<br/><br/>接近价位的全无冲键盘没几个选择的，也就PLU G3000了，牌子是更差了，价格却更贵了。不过键帽是POM材质的，也还算物有所值。然后上tb买，结果……尼玛啊，还是各种缺货！虽然G3000键盘到处都有，可是不是黑轴就是红轴，反正就是没茶轴，连青轴都买不到！机械键盘国内的渠道到底是尼玛怎么回事啊！等了半个月，还是买不到，又一次晕了，好吧，再换。<br/><br/>预算已经被逼着升了差不多100了，这下能买noppoo和ducky的贵些的货色了。于是锁定了noppoo choc pro。话说之前总感觉choc和1008一个档次的，价格怎么这么高？看了下，又是POM键帽，好吧你赢了。本来没打算用如此高级的键帽的，普通的ABS塑料足矣，结果逼着我选了POM的键盘啊。<br/><br/>最终的最终，入了：<br/><img src="http://sandy-sp.com/blog/attachments/month_1110/w20111027232217.jpg" border="0" alt=""/><br/>（配图其实不太正确，这图是choc的，不是choc pro的，不过外形比较接近）<br/><br/><span style="color:Brown">德国cherry MX茶轴。<br/>104全键位无冲突。<br/>POM暗金刻键帽。<br/>镀金USB接口。<br/>钢板背板，相当沉，很稳固。</span><br/><br/>在tb上买东西的坏习惯：买了大件，为了充分利用运费，总想搭上件什么小件商品。<br/>于是想到既然买了机械键盘，总得折腾下换个键帽啥的吧。。于是就蛋疼的顺便买了套K-FORCE键帽：<br/><img src="http://sandy-sp.com/blog/attachments/month_1110/a20111027232715.jpg" border="0" alt=""/><br/>ABS的，不要指望有什么好的档次了，仅仅为了让键盘看起来更像个游戏键盘而已。。<br/><br/>键盘键帽连拔键器等其它附件加起来还有运费，总共让腰包损失了￥550大洋。唉，败家啊。。<br/><br/><br/>不过收获也很大，这下玩游戏彻底爽了。<br/>为了测试下是否真的如传说中的那样全键位无冲突，自己专门写了一个小程序来检测：（下载链接请稍候）<br/><br/>运行小程序检测的时候，结果才发现，我只有十个指头。于是把fm召唤过来，两个人20个指头，达到了如下的结果：<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1110/p2011102811512.PNG" border="0" alt=""/><br/>↑窗口中显示了当前检测到哪些键按下<br/><br/>我想，到了这步足够说明问题了吧。。累死了，松手。。<br/><br/><br/><span style="color:Pink">后面的慢慢写，囧，发现我一篇blog唠叨得都顶别人好几篇了，要不要拆开呢。。</span><br/><br/><br/><script>Hidden('kss6mlovld')</script><div class="UBBPanel" id="hidden1_kss6mlovld"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="显示被隐藏内容"/> 显示被隐藏内容</div><div class="UBBContent"><br/><strong>0x0e，入硬盘。</strong><br/><br/><br/><br/>（待续。）</div></div><div class="UBBPanel" id="hidden2_kss6mlovld"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="隐藏内容"/> 隐藏内容</div><div class="UBBContent">该内容已经被作者隐藏,只有会员才允许查阅 <a href="http://sandy-sp.com/blog/login.asp">登录</a> | <a href="http://sandy-sp.com/blog/register.asp">注册</a></div></div><br/><br/><br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=27</link>
			<title><![CDATA[百元级USB3.0优盘评测：是时候了吗？]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[小.研究发现成果]]></category>
			<pubDate>Wed,08 Jun 2011 11:11:32 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=27</guid>
		<description><![CDATA[<strong>百元级USB3.0优盘评测：是时候了吗？</strong><br/><br/>最近缺一个8G的U盘，想想自己的板子好歹支持USB3.0的接口，不用下怎么行，遂打算入一个8G的USB3.0的。既然买了显然要顺手测一测，于是就有了这篇“民间评测”文章<br/><br/>于是首先选择的是威刚的S102：<br/><img src="http://sandy-sp.com/blog/attachments/month_1106/c201168102725.jpg" border="0" alt=""/><br/>外观是不错的，比较有科技感，个头稍大了点但我倒不在意。<br/>主要不太爽的是两个小功能的缺失：1、没有写保护开关，这是个实用的功能，但却是如今大多数U盘都缩掉的一个功能，遗憾。2、没有指示灯。连这个也缩掉就有点不可思议了，这下我就连通过瞥一眼U盘来确定它是否正在被读写都无法做到了，唉。<br/><br/><br/>官方的广告数据：<br/><img src="http://sandy-sp.com/blog/attachments/month_1106/p201168102937.jpg" border="0" alt=""/><br/>当初决定买威刚S102很重要的一个理由就是看中了宣称这个速度。<br/><br/>可是实际如何呢？来看看接下来的测试吧：<br/><br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1106/7201168103146.PNG" border="0" alt=""/><br/>↑这是我的S102插在USB2.0接口下的ATTO Benchmark测试成绩。<br/><br/>读取速度还是不错的，达到了32M/s，可是写入速度就令人很无语了，只有不到12M/s，甚至比许多USB2.0的U盘还差。<br/>这个会是USB2.0接口的限制吗？对于写入速度，看来不像是接口的性能限制，因为即使再咋滴USB2.0也不可能只有12M/s的带宽，明明我的老U盘都可以达到16M的写入速度的，显然不会是USB2.0接口的问题。<br/><br/>接下来USB3.0接口上的测试也证明了这一点：<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1106/k201168103756.PNG" border="0" alt=""/><br/>↑这是我的S102插在USB3.0接口下的ATTO Benchmark测试成绩。<br/><br/>可以看到很明显的两点：<br/>1、非常令人欣喜的读取速度。USB3.0终于发挥了作用，而且也和宣传上的差距不大。<br/>2、惨不忍睹的写入速度。在USB2.0下就已经很可怜，在USB3.0下没有任何实质性的提高，和那强大的读取速度相比，真是极端悲剧。<br/><br/>最后实际的拷贝文件测试也大体上证明了这点：<br/>拷贝一个700M的视频到U盘中，花费了61秒，平均11.4M/s，USB2.0接口和USB3.0接口下成绩都是如此。<br/><br/>这里有个小插曲，想起威刚官方广告中明明宣称30M/s的写入速度，觉得也不能这么忽悠，于是回头去看，结果发现，广告中在各种不起眼的小角落中标明了“32G版”————哦，原来如此，也就是说这个速度是S102的32G版才能达到的速度，而小容量的版本则会有明显的速度缩水。<br/>这个……怎么说呢，只能说商家的marketing永远都是精明的（农企可能除外- -b），你要说它虚假宣传吧，它也没有作假，但不可否认这个广告的确有一种很强烈的误导：人们想当然的就会认为16G、8G的S102的写入速度也是这个样子的。可惜事实是残酷的，只能说对广告还是要小心谨慎。<br/><br/><br/>想想你平常都怎么用U盘？如果是从一个地方拷贝一堆大文件到另一个地方，这样的话这个号称USB3.0的U盘对你几乎没有实用性：它那惨不忍睹的写入速度会带来相当漫长的拷贝时间，虽然之后拷出来的时候会快一些，可是节约的时间很明显无法弥补之前的时间损失，会算数的不妨自己算算:b ，因此远不如买一个读写均衡的USB2.0优盘划算。<br/>如果你平常只拷贝一些零星小文件，那么更没有必要买它了，便宜的USB2.0优盘足以满足你的需求。<br/>这个U盘只对那些一次写入，多次读取，而且读取的地方必须有USB3.0接口的人才有实际意义。<br/><br/><br/><strong>总结：</strong><br/>威刚S102，外观不错，读取速度很赞无愧于USB3.0优盘的称号，但那残废的写入速度却是它的致命伤，不仅远远达不到官方宣称的写入速度，甚至也不如很多USB2.0的U盘。作为一个以USB3.0为卖点的U盘，我不得不说，它是失败的，残废的写入速度远远掩盖了它出色的读取速度的优势。<br/><br/><br/>8G版S102那写入速度我完全无法忍受，于是换之，换来的是另一个USB3.0产品：<br/>朗科U903 8G版。<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1106/y201161014135.png" border="0" alt=""/><br/>外观，个人觉得不算太好，和威刚的S102比起来显得土气了些。<br/>同样的，这个U盘也没有带有写保护开关和指示灯，也是一大遗憾。印象中朗科作为U盘老大，在这些细节上一向都是很重视的，曾经很长的时间里市面上能买到的带写保护开关的U盘就只有朗科的，当时对此赞不绝口，现在看来即使老大也开始缩水啊。<br/><br/>官方的广告上貌似没写明具体的数据，不过许多网店上给出的速度是：<br/>读取：Up to 60M/s<br/>写入：Up to 30M/s<br/>看起来也很给力的速度，不过实际如何呢？<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1106/h2011610141837.PNG" border="0" alt=""/><br/><span style="font-size:5pt;line-height:100%;">↑</span>这是我的U903插在USB2.0接口上的ATTO Benchmark测试成绩。<br/><br/>可以看出，写入速度的确相比上面威刚S102快了不少，达到了22M/s。<br/>读取速度也不错，达到了30M/s，虽然相比S102略慢一点，但在USB2.0下面达到这样的速度也很不错了。<br/><br/>总体看来这个U903的速度在USB2.0的U盘中是非常优秀的。<br/><br/>接下来是USB3.0接口上的速度测试：<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1106/72011610142116.PNG" border="0" alt=""/><br/><span style="font-size:5pt;line-height:100%;">↑</span>这是我的U903插在USB3.0接口上的ATTO Benchmark测试成绩。<br/><br/>这个结果就比较令人惊讶了，根本和USB2.0下的速度没有本质的区别！读取速度有很小的提升，小文件的读取速度提升的较多一些，写入速度则基本没什么变化。虽然总体是有提升，但完全是可以忽略的级别，和S102读取速度那种从30多M/s到50多M/s的飞跃比起来简直是微不足道。<br/><br/><strong>总结：</strong><br/>对于朗科U903，你基本上可以把它当成一个性能优秀的USB2.0优盘来用。总体来说，读写性能都比较均衡，不像上面S102那样严重偏科，拷贝文件不会存在写入时间太久的问题。但作为一个USB3.0为卖点的U盘，我也不得不说，它是不合格的。在USB3.0的模式下和USB2.0根本没有本质上的差异，不禁让人怀疑它是否仅仅是把接口涂了个蓝色就拿出来当USB3.0优盘卖。<br/>总之，这个U盘日常使用不会有什么问题，但是考虑到价格，就显得不怎么划算了。毕竟它是作为一个USB3.0的U盘来卖的，价格比市面上的USB2.0优盘要贵几十元，但却并没有换来性能的提升，因此除非未来价格降到和USB2.0优盘一样，否则不推荐购买。<br/><br/><br/><span style="color:blue">至此，简要的评测完两种百元级的USB3.0优盘，得出的结论无疑是失望的：<strong>它们都没有达到USB3.0优盘该有的性能，更多的往往是噱头。</strong>标题上的“是时候了吗？”现在看来要给一个否定的回答了，看来还得等第二批更加成熟的USB3.0优盘上市，才到真正购买它们的时机。</span><br/><br/>（未完待续）<br/><br/><br/><br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=25</link>
			<title><![CDATA[折腾电脑简记……]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[日常记录]]></category>
			<pubDate>Mon,21 Mar 2011 20:52:13 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=25</guid>
		<description><![CDATA[去年x月卖本，本来打算是把卖本的钱拿来再添点进去换个新本，结果被snoopy进行了几次忽悠之后（他没多久前才入了个台式，理由是“要打星际2”），遂改买台式。本来自己也确实有入台式的打算，但没想到被忽悠的这么快买了，而且连本也不要了。<br/><br/>y月z日，中关村e世界，某店，购机一台：<br/>CPU：AMD Athlon X3 440（盒）<br/>主板：GA-880GA-UD3H<br/>内存：kingston DDR3 1333 2G<br/>硬盘： WD 蓝 1T<br/>显卡：无<br/>光驱：无<br/>电源：TT XP420<br/>机箱：CoolerMaster 毁灭者<br/>总计：￥2400<br/>看看整个的价钱，有点不可思议的便宜，不过想想这玩意还只是半成品也就高兴不起来了。<br/>同日，在鼎好某神秘角落以￥14xx的价格入了台U2311H显示器，这样一个能用的台式机就完成了。<br/><br/>当然配那么烂的内存以及不要显卡是有原因的，传言内存在降价，打算日后看看情况再升级。至于显卡。。看了很久的5770，由于中关村的价格实在不给力于是放弃购买。<br/>回头看看当时比较失策的地方是：1、散热器没弄个好的；2、电源选得偏小<br/><br/>之后作为比入门级还入门的DIYer就开始了漫长的折腾过程：<br/>------------------------------------------------------------------------<br/><br/><img src="http://sandy-sp.com/blog/attachments/month_1103/e201133122150.jpg" border="0" alt=""/><br/><strong>0x01，首先当然是开核啦。</strong>买盒装的U就是为了撞狗屎运，避免买被挑剩下的。貌似还真撞上狗屎运了，不仅开4核ok，连L3 cache也成功开启，这下U直接升级成Phenom II X4 940了，免费的，嗯。<br/><img src="http://sandy-sp.com/blog/attachments/month_1103/w2011331223658.jpg" border="0" alt=""/><br/><br/><br/><br/>------------------------------------------------------------------------<br/><img src="http://sandy-sp.com/blog/attachments/month_1103/b2011331221533.jpg" border="0" alt=""/><br/><strong>0x02，超集显。</strong>那个可怜的HD4250被我驱使着去跑H.A.W.X这种级别的游戏，显然吃不消啦，于是超20%用之，可惜大游戏中仍然不给力（废话么），同时继续盼望5770降价，可是丫的这货价格坚挺如乔戈里峰一般，无奈。<br/><br/><br/>------------------------------------------------------------------------<br/><strong>0x03，装独显。</strong>某日snoopy实在看不下去了，提出将他的GTS250借我用一阵子，于是换上了XFX的GTS250 512M。终于可以爽一把了。话说装好后第一次开机那“嗡”的一下如吹风机一样的风扇声音吓了我一跳，还好装了驱动终于不吵了。<br/><img src="http://sandy-sp.com/blog/attachments/month_1103/52011331223858.jpg" border="0" alt=""/><br/><br/><br/><br/>------------------------------------------------------------------------<br/><strong>0x04，败音箱。</strong>实在听不下去从snoopy那里借来的山寨音箱的杂音了，于某月某日在淘宝上￥400大洋败了一款漫步者C2，全木质结构三分频独立功放2.1+1，虽然那些音乐爱好者看不上眼但对咱这种土鳖来说这已经是高档装备了。。<br/><img src="http://sandy-sp.com/blog/attachments/month_1103/t2011331224150.jpg" border="0" alt=""/><br/><br/>PS那个旋钮真的很好用。<br/><img src="http://sandy-sp.com/blog/attachments/month_1103/72011331224213.jpg" border="0" alt=""/><br/><br/><br/><br/>------------------------------------------------------------------------<br/><strong>0x05，装无线网卡。</strong>你别说，我自己也觉得有够蛋疼的。台式机装无线网卡，真是折腾。为啥不用有线？因为线太不好走了。先京东上入了块一个诡异的品牌“totolink”的无线网卡，主要看中它那可以单独布局的天线，结果回来试了一天发现信号巨差无比，只有1格而且很不稳定，于是换之，换成了TPLink的781n，虽然信号仍然不太好（2格），但至少稳定了。<br/><img src="http://sandy-sp.com/blog/attachments/month_1104/s20114102823.jpg" border="0" alt=""/><br/><br/><br/><br/>------------------------------------------------------------------------<br/><strong>0x06，卸独显。</strong>某日snoopy终于忍不要玩SC2了，于是这块XFX就物归原主。卸下来后第一次开机那安静的氛围又把我吓了一跳，还以为机器点不亮了，再想想原来是没那个吹风机般的显卡风扇了。。<br/><br/><br/><br/>------------------------------------------------------------------------<br/><strong>0x07，大败家。入了块远远超出预计的显卡，升级了内存。</strong><br/>实在受不了用HD4250来玩游戏，也实在等不及5770降价了，干脆咬牙调预算+200，入460 768M。在淘宝上对比来对比去，折腾了几天，最终决定选Inno3D的GTX460游戏战神，淘宝某看起来比较靠谱的网店标价1050，正打算入，结果在CHH上看到一帖——迪兰疯了出了块本年度最给力卡“5850恒金”，价格1070，哇靠，立刻瞎了，这可是货真价实的5850诶，再怎么缩也是5850啊。听说货源紧张，于是周末立马跑到中关村1070秒了块回来。<br/><img src="http://sandy-sp.com/blog/attachments/month_1104/v20114102742.jpg" border="0" alt=""/><br/>凑巧这时候内存价格已经探底了，于是把以前那破金士顿1333出了，+280差价入了贼船DDR1600 2G*2。<br/><img src="http://sandy-sp.com/blog/attachments/month_1104/420114102854.jpg" border="0" alt=""/><br/><br/><br/>------------------------------------------------------------------------<br/><img src="http://sandy-sp.com/blog/attachments/month_1104/t201141183822.jpg" border="0" alt=""/><br/><strong>0x08，折腾。</strong>由于显卡远远超出之前预计的等级范围，于是电源就变成悲剧了——350W在理论上说是带不动X4 940加5850的。还好理论和现实是有差距的，于是我的电源得以继续维持工作，但也担心夏天了可能会引起罢工，遂开始研究400~500W之间的电源。。<br/>OC这个CPU的时候，发现不太给力，弄到3.35G就不行了。OC这个显卡的时候也不太给力，弄到820MHz的时候就不行了。不过自己也都满足了：不到500元入一个全开4核带6M L3的CPU，还想咋？1000出头入了块货真价实的5850还是一线的，还想咋？知足吧。。<br/><br/><br/><br/><strong>0x09，入风扇，今天。</strong>冬天的时候我的那个可怜的Athlon原装风扇压全开的CPU还没什么问题，天气温度稍微升了点，就开始悲鸣了。玩COD和DMC的时候挖槽，那声音吵死了，开始还以为是显卡声音太大，后来仔细定位才发现原来是CPU的散热器撑不住了：动不动就飙到5000转，OMG，再高就要挂了。毕竟Phenom比Athlon的TDP要高30W啊，看来不换扇子不行了。考虑到俺简陋的机箱内没有其它风扇能给内存和北桥散热了，只好放弃侧吹式风扇而用下压式，看来看去纠结于几个型号，一直不能决定。最终，今天顺路去中关村，一咬牙顺便把扇子入了，于是最终弄了一个CoolerMaster 旋风V4+，￥105，四热管10cm风扇，看起来还凑合，不过掂份量怎么感觉还没九州风神beta 400重啊（错觉？）。<br/><img src="http://sandy-sp.com/blog/attachments/month_1104/u201141183955.jpg" border="0" alt=""/><br/><br/>回来安装的时候遇到了些麻烦，老散热器拔不下来。。。感觉和U粘死了，心理嘀咕该不会当初装机那家伙给我抹的是硅胶而不是硅脂吧？之前见过一个这么悲剧的哥们，CPU牢牢粘在散热器底部，比502还结实，那家伙暴力拆风扇的时候居然连U一起给拔下来了，还把一个针弄歪了……心想我这次不会这么悲剧吧，反正暂且别暴力的好，于是把风扇装回去然后玩游戏。。过一阵子听风扇比较吵了，U估计也热的差不多了，这时候立刻关机，拆箱，拔风扇，这次居然一下拔下来了，看来之前果然是硅脂。。于是装新散热器，有点小悲剧的是它比原装的大太多了，特别是热管的位置，和扣具底座有一定冲突，最终装上去之后散热器底座和CPU是有一定偏移的，并没有100%盖住CPU表面。不过漏出来的很小的一部分是边缘，应该影响不太大，开机运行测试正常。<br/>如今玩大游戏再也不会听到那可怕的声音了，这10cm的风扇最快就2100rpm就可以压住我的CPU了，虽然仍然能听到声音但比起之前还是小多了。。不过郁闷的是待机时的声音貌似却比之前用原装风扇的稍微大点。。嗯。。<br/><br/><strong>0x10，估计下次就是要折腾电源了。。</strong><br/><br/><br/>回头来清点下各种折腾的开销：<br/>最初：&nbsp;&nbsp;2400<br/>败音箱：400<br/>加网卡：100<br/>败显卡：1070<br/>升内存：280<br/>换风扇：105<br/>-------------<br/>总计：4355<br/>这么算下来这主机也不便宜哈……再加上俺的U2311H，就差不多5800多了。咦不对，这个数怎么比我写这文章之前预计的低很多？哦，那还是挺便宜的啊？哦，哦……看来折腾还是有点意义的啊。。不到4k RMB弄了台能在1080p分辨率下基本玩爽所有游戏的主机，也还凑合了。。<br/><br/>只是浪费了不知多少时间与脑细胞。。还乐此不彼。。悲剧的DIYer们啊。。<br/><br/>唉，生命在于折腾。。<br/><br/>最后提醒诸位：慎入CHH，否则被毒害的穷三代了后果自负。。。- _-b<br/><br/><br/><br/><br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=24</link>
			<title><![CDATA[C++中利用RAII在stack上管理资源]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[技术文章]]></category>
			<pubDate>Fri,11 Mar 2011 23:45:40 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=24</guid>
		<description><![CDATA[<strong>C++中利用RAII在stack上管理资源I</strong><br/><br/><br/>众所周知C++的资源管理与释放是个比较麻烦的东西。C++没有GC等自动回收资源的机制，我们需要自己善后申请出来的各种资源。<br/><br/>从最简单的堆上创建对象来说起吧。在某大互联网公司内，经常可以看到这样的代码：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH0" >int do_sth()<br/>{<br/>&nbsp;&nbsp;int ret = 0;<br/><br/>&nbsp;&nbsp;char* buffer = new char[BUFFER_SIZE];<br/>&nbsp;&nbsp;XXObject* pObj = new XXObject();<br/>&nbsp;&nbsp;<br/>&nbsp;&nbsp;YYObject* pObj2 = pObj-&gt;Cr&#101;ateYY(buffer);<br/>&nbsp;&nbsp;if(!pObj2)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;failed to Cr&#101;ate YY&#34;);<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te buffer;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj2;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te buffer;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work2();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work2() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj2;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te buffer;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;return 0;<br/>}<br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH0","cpp" );</script><br/><br/>这只是个示意代码，不过姑且看看。<br/><br/>我们看到，这个函数的本质其实就是创建了两个对象和一块缓冲，然后调用了上面的两个方法而已，但是代码却写了这么大一堆。其中大部分是错误处理代码，而且这些之中的大部分则是资源的销毁，而且不用我说，谁都看出来了这些销毁代码有大量的重复。<br/>重复就导致了不一致的可能性进而维护困难。比如随着这个函数的加长或修改，就很容易漏掉其中的某几个资源销毁。<br/>另外这种代码，阅读起来也不太舒服：本来函数的逻辑很简单，但是却被一大堆的琐碎细节（错误处理，资源释放等）所塞满，让整个函数看起来臃肿无比。<br/>其实关键就那么几句话，但是每句下面都跟了一堆繁杂的东西。因为这个函数只是个示例，看起来可能觉得还没什么，但是实际上某公司的代码中这个函数比这个大多了（虽然其编码规范上写着“不应有过大的函数”），经常会有十来个调用，每个调用后面都跟一堆这种繁琐的玩意，看得人头大，恨不得找个代码折叠工具把这些都折起来。好的代码应该是“自折叠”的，也就是说每一层级的代码应该能够很恰当的只显示当前focus on的层级的逻辑，而隐藏掉更高层或更低层次的代码。<br/><br/>于是就有人想要改进。比如某公司就提出了“goto不是洪水猛兽”，从而有了以下代码：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH1" >int do_sth()<br/>{<br/>&nbsp;&nbsp;int ret = 0;<br/><br/>&nbsp;&nbsp;char* buffer = NULL;<br/>&nbsp;&nbsp;XXObject* pObj = NULL;<br/>&nbsp;&nbsp;YYObject* pObj2 = NULL;<br/><br/>&nbsp;&nbsp;buffer = new char[BUFFER_SIZE];<br/>&nbsp;&nbsp;pObj = new XXObject();<br/>&nbsp;&nbsp;<br/>&nbsp;&nbsp;pObj2 = pObj-&gt;Cr&#101;ateYY(buffer);<br/>&nbsp;&nbsp;if(!pObj2)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;failed to Cr&#101;ate YY&#34;);<br/>&nbsp;&nbsp;&nbsp;&nbsp;goto err;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;goto err;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work2();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work2() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;goto err;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;return 0;<br/><br/>err:<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te pObj2;<br/>&nbsp;&nbsp;&nbsp;&nbsp;del&#101;te buffer;<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>}<br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH1","cpp" );</script><br/><br/>换成了这种风格，利用goto的无条件跳转，把各种销毁动作都定向到了一起，然后利用del&#101;te NULL不会引起错误的特点来合并一些销毁条件。<br/><br/>当然这个风格有许多人会觉得不爽。当然不爽了，它用了goto这个被以Dijkstra前辈为首的许多人视为大忌的东西，而且也确实导致了一些问题：比如为了避免跳跃初始化，所有的变量都必须在函数开头定义了。这下直接把C++变pascal了，虽然有人会说这种定义方式“优美而有条理”，可是实际上大部分情况下变量还是应该就近定义，从而保证代码的局部性。<br/><br/><br/>或许还有人会给出第三个版本，利用try...catch结构（或古典C风格的do...while(0)结构）来优化上述代码。不过先不管许多项目中是避免使用try...catch的（do...while(0)也只能用于一些最简单的情况），即使用，它仍然需要涉及到一堆对象的回收问题。<br/><br/><br/>ok，扯了这么多无关的东西，还没有说到点子上，什么是RAII。<br/><br/>RAII：Resource Acquisition Is Initialization——“资源获取就是初始化”。某百科中的一句话解释是：这是一种利用对象生命周期来控制程序资源的简单技术。<br/><br/>名字听起来挺ooxx的，其实完全不用理会这冗长的解释，其实本质上是一个很简单的东西。<br/><br/>回头看看我们上面的代码。<br/><br/>其实仔细看看，<span style="color:Brown">最主要的问题在于，我们在堆上申请了资源，从而无法利用栈的自动弹出特性来做到自动回收</span>。<br/>我们new出了一堆东西，这些东西在函数结束的时候不会自动回收，必须手动del&#101;te，这是关键。<br/>如果是直接在函数栈上分配的各种变量，就简单了，任何时候退出函数，这些变量自动失效被回收。要是堆上分配的对象能和栈上的一样听话那多好啊！我们能不能把堆上分配的对象使用栈的方式来进行管理呢？<br/><br/>其实很简单，就是<span style="color:Brown">用一个栈上的小对象来维护这些new出来的大对象</span>，栈上的小对象被回收的时候，顺便在其析构函数内回收其管理的大对象就ok了。<br/><br/>最简单做法，就是使用一个智能指针：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH2" >int do_sth()<br/>{<br/>&nbsp;&nbsp;int ret = 0;<br/><br/>&nbsp;&nbsp;std::vector&lt;char&gt; buffer;<br/>&nbsp;&nbsp;buffer.reserve(BUFFER_SIZE);<br/>&nbsp;&nbsp;std::auto_ptr&lt;XXObject&gt; pObj(new XXObject());<br/>&nbsp;&nbsp;std::auto_ptr&lt;YYObject&gt; pObj2(pObj-&gt;Cr&#101;ateYY(buffer));<br/>&nbsp;&nbsp;if(!pObj2)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;failed to Cr&#101;ate YY&#34;);<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;ret = pObj2-&gt;work2();<br/>&nbsp;&nbsp;if(ret)<br/>&nbsp;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;LOG_WARNING(&#34;call YY::work2() failed, err:%d&#34;, ret);<br/>&nbsp;&nbsp;&nbsp;&nbsp;return -1;<br/>&nbsp;&nbsp;}<br/><br/>&nbsp;&nbsp;return 0;<br/>}<br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH2","cpp" );</script><br/><br/>使用了标准库中的auto_ptr这个指针来当作一个“栈上的小对象”，用它来管理各种堆上的大对象或者其它资源，这样我们就可以直接在函数中间return，跳出函数时各个栈上的智能指针会被自动销毁，而它们所持有的资源也会一并被销毁。整个代码一下子变得简洁清晰。<br/><br/>这其实便是一个使用RAII来管理资源的简单应用。虽然RAII全称是“资源获取就是初始化”，其实在这里我们用的反倒是它的对应面——“资源销毁就是析构”，呵呵。<br/><br/>当然有人可能又对auto_ptr皱眉头了，的确，这个指针很多时候不那么讨人喜欢，因为其有奇怪的“转移”语义，容易被误用，因此在有些项目中甚至被禁用。（最新的C++11标准已经把auto_ptr扫地出门了，用unique_ptr代之）<br/><br/>没关系，如果你对auto_ptr很过敏，那么也不影响在这里RAII的应用：反正我们只是要一个能在栈上管理资源的小对象而已，完全没有必要兴师动众弄个auto_ptr，自己来一个更简单更安全的东西就足矣：<br/><table width="100%" border="0" cellpadding="0" cellspacing="0" style="margin-top:2px;margin-bottom:2px;"><tr><td class="UBBTitle">C++ 代码：</td></tr></table><div class="UBBPanel codePanel UBBContent" id="UBBdCodeH3" >template&lt;typename T&gt;<br/>class RAIIPtr<br/>{<br/>public:<br/>&#160;&#160;&#160;&#160;typedef T element_type;<br/><br/>&#160;&#160;&#160;&#160;RAIIPtr() : m_ptr(NULL){};<br/>&#160;&#160;&#160;&#160;explicit RAIIPtr(T* ptr) : m_ptr(ptr){};<br/>&#160;&#160;&#160;&#160;~RAIIPtr()<br/>&#160;&#160;&#160;&#160;{<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;del&#101;te m_ptr;<br/>&#160;&#160;&#160;&#160;}<br/><br/>&#160;&#160;&#160;&#160;T* get() const{ return m_ptr;}<br/>&#160;&#160;&#160;&#160;T&amp; operator * (){ return *m_ptr;}<br/>&#160;&#160;&#160;&#160;T* operator -&gt;() const { return m_ptr; }<br/><br/>&#160;&#160;&#160;&#160;T* release()<br/>&#160;&#160;&#160;&#160;{<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;T* p = m_ptr;<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;m_ptr = NULL;<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;return p;<br/>&#160;&#160;&#160;&#160;}<br/><br/>&#160;&#160;&#160;&#160;void reset(T* ptr)<br/>&#160;&#160;&#160;&#160;{<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if (ptr == m_ptr) { return; } <br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;del&#101;te m_ptr;<br/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;m_ptr = ptr;<br/>&#160;&#160;&#160;&#160;}<br/><br/>private:<br/>&#160;&#160;&#160;&#160;RAIIPtr(const RAIIPtr&amp; v); // 禁止拷贝构造<br/>&#160;&#160;&#160;&#160;RAIIPtr&amp; operator = (const RAIIPtr&amp; v); // 禁止赋值<br/><br/>&#160;&#160;&#160;&#160;T* m_ptr;<br/>};<br/><br/></div><script language="JavaScript">CodeHilight("UBBdCodeH3","cpp" );</script><br/><br/>这是个最简单的智能指针，只具备持有一个资源并自动回收它的功能，并且为了安全禁止将其所有权转移。这么看似极其简陋的玩意，却恰如其分的实现了在栈上自动管理资源的功能，非常适合于上述场景的应用。<br/><br/><strong>其实，只要将上面我们写的这个小小指针的名字改为scoped_ptr并且再加以完善，就是大名鼎鼎的boost::scoped_ptr了。</strong><br/><br/>scoped_ptr是boost库提供的几种智能指针之一，初看它许多人都会觉得功能太弱了，但实际上却是在编程中非常好用的一种指针。不像boost中的某些过于tricky的库（如lambda之类的。。），smart_ptr中的几个智能指针可的确是货真价实童叟无欺的各位先辈大牛们从日常各种coding中总结出来的实用玩意啊。<br/><br/> <br/>当然，如果你已经用上了最新的C++11的话（VC2010和GCC4.5支持的较好），强烈推荐使用标准库中的unique_ptr，它是scoped_ptr与auto_ptr的合体，而且完全消除了auto_ptr的各种不妥与隐患，既简单又高效，很实用。<br/><br/><span style="color:Pink">（待继续完善）</span>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=23</link>
			<title><![CDATA[blog修整计划1.0]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[虚无]]></category>
			<pubDate>Mon,07 Mar 2011 18:41:09 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=23</guid>
		<description><![CDATA[blog修整计划1.0<br/><br/>1.追查meta数据无效的问题。优化编辑界面。<br/>meta数据无效问题已经追查到，貌似pj本来就是如此设计的，每篇文章的自定义meta实际上是无用的，蛋疼啊。要改动还涉及到一些组织结构方面的问题，稍有麻烦。同时也再一次感觉到pj的代码并不是写的很好。<br/>既然那个meta实际上是没用的，编辑界面里面占如此大面积的meta框就可以去掉了，当然也不想完全撤掉，说不定哪天有心情了会把meta改好呢，于是改成了可以自动隐藏与展开的框，平时也不占地方，不过未经仔细测试。<br/><br/>后来又发现，编辑器的各种问题比我预想的多得多。丫的PJ的UBB编辑器貌似压根没考虑FF和chrome下的使用，许多功能直接无效。不过好笑的是，JS代码居然还分成了IE和Gecko两套，可惜Gecko那套里面许多功能仅仅是敷衍，有些函数则根本未予以实现！真是服了，堂堂大<a href="http://www.pjhome.net" target="_blank">PJBlog</a>的编辑器居然是这么个货色，只好自己边查边试改之，最后好歹在chrome下这个编辑器终于和IE下一样可用了。<br/><br/>2.完成上下行同步功能。<br/>--进行中<br/><br/>3.实现全新tag系统。<br/>--规划中<br/><br/>4.实现多主题地址映射。<br/>--未开始<br/><br/>5.宽屏大字体主题的制作。<br/>--未开始<br/><br/>6.文章版本控制<br/>--非优先<br/>]]></description>
		</item>
		
			<item>
			<link>http://sandy-sp.com/blog/article.asp?id=21</link>
			<title><![CDATA[n长时间以来的日常记录]]></title>
			<author>sandy_sp@qq.com(sandy1)</author>
			<category><![CDATA[日常记录]]></category>
			<pubDate>Mon,07 Mar 2011 18:35:07 +0800</pubDate>
			<guid>http://sandy-sp.com/blog/default.asp?id=21</guid>
		<description><![CDATA[n长时间以来的日常记录<br/><br/>很久没写了，也该补补了。特别是之前某事件导致的我大批日记丢失，这个补的跨度就增大了好多，唉，将就着回想补点吧。<br/><br/><script>Hidden('yqugkcp1m8')</script><div class="UBBPanel" id="hidden1_yqugkcp1m8"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="显示被隐藏内容"/> 显示被隐藏内容</div><div class="UBBContent">please stand by...</div></div><div class="UBBPanel" id="hidden2_yqugkcp1m8"><div class="UBBTitle"><img src="http://sandy-sp.com/blog/images/quote.gif" style="margin:0px 2px -3px 0px" alt="隐藏内容"/> 隐藏内容</div><div class="UBBContent">该内容已经被作者隐藏,只有会员才允许查阅 <a href="http://sandy-sp.com/blog/login.asp">登录</a> | <a href="http://sandy-sp.com/blog/register.asp">注册</a></div></div>]]></description>
		</item>
		
</channel>
</rss>

