善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。

2017-05-31

淘宝天猫都碰不得啊

前言
六一儿童节快到了,给儿子买点什么礼物好呢?虽然前不久刚买了一架 UH-60 和一把 Glock17,但我还是想买点对儿童稍微有点教育意义的东西。思来想去,觉得地球仪不错。正好暑假也准备带家人出国去转转。若是连自己去了哪里却一点概念都没有,那岂不是很遗憾?

平时工作也算得上忙,所以我就偷懒打算在网上购物。碰巧近来网购的体验还不错,正所谓好了伤疤忘了痛,我又开始在淘宝上逛了。

其实以前我是很吃过几次亏的,所以也曾经痛下决心告诉自己大部分东西都不能在淘宝上买了。但前不久那次买玩具的感受实在是不错:我擦!居然有金属的 Dragunov?这次一翻地球仪,我更是震惊了:我擦!居然还有磁悬浮的?!

所以说啊,人必须时刻保持冷静,才能抵挡诱惑。头脑发热之下,我真的就下单买了个所谓的「磁悬浮地球仪」。带着残存的一丝丝理智,我选了一家天猫店下单。我觉得吧,C2C 的淘宝不靠谱,B2C 的天猫大概好一点,至少出了问题比较好解决。事后证明,我这想法也对也不对。

糟糕
等待收货的过程还是蛮引人遐想的。然而收到货一拆包装,首先心里凉了一半:包装倒没有什么特别的问题,不过产品外包装上连半个汉字都没见到,没有品名,没有厂家名称,没有商标,连个 LOGO 都没有。好在我是懂英文,加上有个图片,还能看出来没发错货。
打开盒子,里面除了地球仪和一些填充物,就只有一张非常简单的「说明书」。合格证、保修卡,什么都没有。这它妈的不就是传说中的“三无产品”吗?
好吧,网购的东西,要求别那么高,毕竟地球仪才是主要的。我对它有着不小的期待,因此心里倒也没怎么在意那些「细枝末节」的事情。看了看说明书,通上电源,开始折腾。折腾着折腾着,汗就下来了。
要完成所谓的「悬浮」,好困难啊!

虽然买之前看过一些评论,算是心里有所准备,但还是没想到这么麻烦。送的那个什么「悬浮棒」根本就不好使。还是有个评论里面说用中性笔的办法比较管用。好不容易能够比较顺利地「悬浮」上了,然而只要有一点点扰动,平衡立马就被打破,通常的结局就是地球仪被「铛」的一声吸到顶上。而且就算是很小心地退开,一般也坚持不了几分钟。我发现这大概跟加工精度有关系,因为顶上的磁体是斜的,而我无法去校正。
并且,产品说明里面提到的自动旋转,似乎也无法达到。球体也就是左右来回转,每次角度也就是几十度。我定睛看着它,发现角度在越来越大,正当我满心希望它最终能转一个整圈的时候。「铛」,它又被吸到顶上去了。

这它妈让人怎么用?我不可能每天都花一两个小时的时间在折腾这东西上。睡觉时还不能开着,不然保管神经衰弱。要是只能摆着或吸到顶上,那几十块的普通地球仪岂不是更好?我干嘛买个这么贵的银样蜡枪头?
不行,退货!

退货
在淘宝上买东西,顺利的话一切都好。然而一旦买到糟糕的玩意儿需要退货,那麻烦就来了。我以前的糟糕的购物经历无一不源自于此,这次当然也不例外。当我以「质量问题」为原因要求退货时,卖家拒绝了。我对此也并不意外——反正最糟糕也就是哪样,总不可能直接投降吧。

卖家拒绝的理由看起来很好笑——产品有正规工厂专利进货发票。这话好眼熟啊!对了,我以前在某个论坛上看卖家们交流心得时提到过,如果被投诉三无产品,就这样应对,只要你拿得出发票来,淘宝小二可不管它是不是真的……。呵呵,这样的发票当然可以买。有些买的发票连税务局那边都能过,何况淘宝。淘宝的处理方式很简单,C 说你卖的是假货,你 B 有发票吗?噢,有,那 C 你去证明 B 卖的是假货……
我去你妈的。

这个时候我看到了一个「极速维权」的按钮。我点了一下,叫我提交证明。
我能怎么证明呢?我只能把上面那两张图片传了上去。产品不能正常使用,我能怎么证明?上传一段小电影吗?搞笑。我都能想得到接下来的过程,无非就是叫 C 去找质监局,出具鉴定证明。普通消费者常常到了这一环节就望而却步了。较真的可能会去工商局投诉,然而一般也是了无下文。

要说起来,这个环节是很给「天猫」加分的地方。我刚上传了照片不久,天猫说因为我是「信誉良好」的顾客,所以给我先行赔付了。的确也是马上就是进入到退货发物流的环节了。听明白了吧——东西真不真我不表态,但是我相信你!
很多时候,事情就这么摆平了。你要认真按「假一罚 N」地去索赔,那可就没这么容易了。职业打假人毕竟是少数。这一招就叫做「分化瓦解」、「团结大多数,孤立一小撮」。

给东西重新装箱的时候,我又留意了一下这东西的做工,才发现其实粗糙得很,根本不值 169 元这个价钱。所谓专利号,搜了一下是宁波一家公司的,跟淘宝商家八竿子打不着。如果光看成本,20 元都不一定有。尽管到淘宝上买东西没一个不是想贪便宜,但你一定要相信,卖家不会让你真正占到便宜的。

运费
我记得,我上次在 Blog 上吐槽淘宝的事情,最后问题也是出在退货的运费上。这次又是「也不例外」。可见,如果那里的确有一个坑,你再走几次也还是会掉进去的。

天猫很「贴心」地提供了「上门取件」服务,还可以约时间。说实话,当时我真的有点小「感动」,几乎就要拍手叫好了。

取件的人来的时候,我在上班,是我太太处理的。取完件我立马就收到了退款。然而收快递的人跟我太太说,运费本来是 7 元,但是要收 11 元,因为「你的东西超宽了」。
好吧。我心想,反正质量问题的退货运费是应该由卖家承担的。所以我也就没有坚持太多。收快递的人(我不想用「快递员」一词称呼他,因为我对他有意见,我觉得他不配)叫我支付宝直接付掉,我打开手机的淘宝 App,的确有付款按钮。我点开看,7 元。

怎么回事?也许是对方还没提交新的价格。我就又做了一些手头的工作。一个小时以后再去看,还是 7 元。
7 元就 7 元,我心想,也许系统不觉得我的东西「超宽」。付完款没多久,太太说快递员打电话来,叫补 4 元运费,给了支付宝地址。
妈的,现在想起来补运费了?

结束
讲个笑话:太监下面有什么?
什么都没有了。

质量问题导致的退货,运费不是由卖家承担吗?如何承担?要我「垫付」已经是足够糟糕的体验了,然而现在是要我去找卖家聊IM来「讨」吗?
我最憎恨的就是这种「干点什么都要 IM」的事情。而前几次还算「愉快」的购物体验,也正因为我「不用到 IM 上做任何事情」。

好吧,既然天猫觉得这个事情「到此为止」了,那我对你淘宝 / 天猫也就「到此为止」吧!
其实总结下来,写了这么多,无非是想让自己记清楚这些个教训。有钱留着去国外花,别它妈犯贱去交智商税。

2017-05-25

将 C++11 新特性用于代码优化

关于 C++11 的科普,在这里就不详细进行了,可以参考 维基百科 页面。即使是中文页面,我认为写得足够详细和系统了。

总之,C++11 对原始的 C/C++ 作出了在我看来是不算小的改动。有一些概念,放在以前的时代是绝对真理,在 C++11 推出之后,可能需要重新了解一下了。VS2013 对 C++11 的支持并不算「完美」,不过大部分「有用」的特性还是到位了。这里就以它为例,来谈谈如何把 C++11 的新特性应用到你的软件开发工作中来提升性能和开发效率。

本文提到的 C++11 的这些新特性,我大致把它们分为两类:一类是可以直接提升代码的性能表现的,我列在「性能优化」部分;另一类虽然不能直接提升代码的性能,但可以提升开发效率,便于更快地开发出可维护性更好的代码,我列在「非性能优化部分」。

另外,受作者水平所限,本文并不是对 C++11 在这些方面的完整的参考内容,仅仅作为一个引导来阅读吧。


性能优化部分

右值引用和 move 语义
C++11 引入了右值引用,支持了 move 语义。在我看来,这个变化的意义可能是 C++11 里面最大的一个。右值引用和 move 语义是什么,这里不展开。通俗一点地讲,这个特性使得程序员可以在必要的时候自行决定到底是深拷贝还是浅拷贝。对于大量的数据「搬运」操作,可以节省下不少时间。对于性能优化来说,意义重大。

其实就算没有右值引用,在 C++11 之前的时代也可以做类似的优化。C++ 程序员只要对于自己的资源管理类显式地提供深 / 浅拷贝版本的函数即可。不过这样一来代码工作量会比较大,程序会变得比较复杂,并且始终不是一个规范。现在这一切都不是问题了。

对于 STL 自己的类/容器,VS2013 已经做了足够的优化。例如,你可以通过:
string strA = std::move(strB);
来把 strB 的字符串动态内存部分直接给到 strA,速度比简单的赋值要快不少。当然,strB 就不再具有有意义的值了(这里例子中会变成空字符串)。当你 push_back 或 insert 一个 string 到容器里面的时候,如果 string 其实是一个临时变量,那么用 move 语义你也可以得到相当明显的性能提升。

如果例子里面 string 换成一个 map<string, string>,那么提升会更明显。总之,内存的分配和释放,以及 memcpy 操作统统被避免了。所以,理论上需要传递的东西越多,你得到的性能提升就会越显著。

就地部署(emplace)
C++11 对于常见的 STL 容器,都提供了一种能提升性能的数据置入方法,称之为「就地部署」。通过用就地部署取代原来的 push_back 或 insert 之类的操作,不再需要先构造再传递,而是由容器直接调用目标对象的构造函数来完成数据填充。

在某些情况下(T 提供了对应的构造函数时),这样可以避免一次拷贝构造的开销。而最差的情况(T 没有提供对应的构造函数),也最多不过就是与 push_back 和 insert 效果一模一样而已。所以我建议所有能用上就地部署的地方,都统统用上,无需太多考虑。

并且,就地部署与 move 语义相互并不冲突,而且是互有补充。move 语义解决深拷贝慢的问题,就地部署试图减少哪怕是浅拷贝的执行次数。两者配合起来效果更加完美。

散列表
在 C++11 里,不再需要通过第三方库来引入散列表(或者叫哈希表)了。STL 正式支持了四种散列表的实现,全部都冠以「unordered_」的前缀,以便与一些第三方实现相区别。

对于大多数用 map / set 实现的代码,只要简单替换容器就可以得到性能上的提升。map / set 基于红黑树(自平衡二叉树),时间复杂度至少是 log(N)。散列表版本的 map / set 提供常数级的时间复杂度,随着数据量的增大,无论是写入还是读取的性能都超过了红黑树版本的 map / set。

我个人的测试结论是:同是 set<string>,即使是小数据量,散列表版读取代价也只是红黑树版的约 60%;小数据量下,红黑树版写入略快,但在容器内数据量达到「万」级别的时候,散列表的写入速度也开始超越红黑树版(此为 Release 版测试结论,Debug 版在「百」级别即发生超越现象)。

所以我认为,只有在数据量很小,并且写入与读取的概率大致相当时,使用红黑树版 map / set 才在性能上可能有明显收益。其余情况,都建议采用散列表版本 map / set。当然,如果 T 是自定义类,并且你不愿意为它写散列函数,那就算了。


非性能优化部分

完美转发
C++11 中所谓「完美转发」的特性,其实是配合右值引用来使用的。如果为了支持右值引用,而不得不让自己的代码量变大一倍,那有些人可能就要望而却步了。完美转发其实是借用了模板技术,使得你可以只写一份代码,就可以兼顾(常量)左值引用与右值引用的情况。工作量更少,代码更简洁,出错的概率也就更低。

不过,采用模板技术的缺点就是:编译期展开。这一方面降低了编译器的效率,另一方面会导致头文件的包含关系变得不太容易整理。除此之外,还有一种我称之为「不完美转发」的替代解决方案,本质上是在性能上作出一定程度上还算可以接受牺牲,来换取代码简洁性,取得一个还算 OK 的平衡。我会另外写一篇 Blog 来介绍一下它。

类型推导
「类型推导」也就是所谓的 auto 类型。这个东西使用起来基本没有门槛。很多人可能最开始接触 C++11 就是通过它了。

这个的确是一个好东西,用来写 STL 的 iterator 类型再合适不过了。因为我们本来也不怎么关心 iterator 的具体类型。不过,仍然不建议滥用。如果到处都是 auto,阅读你代码的人会经常性地需要回顾才能知道一个变量的类型,特别是在你没有用匈牙利命名法的时候更是如此。

所以,我的建议是:当你觉得一个变量的类型写起来很麻烦,而你其实并不关心它的时候,放心地用 auto。并且,auto 变量的作用域不要太大,if / for / while 循环内的局部变量用它是最合适的。

基于范围的 for 循环
很多语言早就可以这样写了。而 C++11 现在也可以这样写了:
for (auto& stk : stocklist)
相比起:
for (auto pIter = stocklist.begin(); pIter != stocklist.end(); ++pIter)
孰优孰劣一目了然。何况后者通常还需要跟一句:
auto stk = (*pIter);
不过,如果是一个 map,你可能经常要取 pIter->first / second 之类。或者你打算在循环里面对 pIter 做 erase 操作,那还是用传统方式比较好。

空指针
用 nullptr 取代 NULL。我觉得最大的好处就是 nullptr 的颜色没有 NULL 扎眼。不过,由于 NULL 也表示 0,有的时候也表示无效句柄。我觉得对于所有指针类型的 NULL,置换成 nullptr 可能会对阅读代码有一定帮助。

角括号
C++11 的编译器现在可以识别 >> 到底是两个模板类的嵌套,还是 >> 运算符。因此写代码的时候就不特意空上一格,写多层模板类嵌套的时候就更美观一点。

不过,多层模板类嵌套,本来就不可能「美观」到哪里去。起码我是不建议太多此类的代码实践的。

初始化列表
vector 可用这样的方式来进行初始化:
vector<int> vecX = { 1, 2, 3, 4 };
的确是比以前省事了。也就是说,C-Style 数组的存在意义又少了一层。

统一初始化
struct 可以被这样初始化:
struct C
{
    int a;
    int b;
    int c;
};
C c{1, 2, 3};
class 的 public 成员也可以。
在某些喜欢使用各种结构体的代码中,这个特性可以让你少写一大堆构造函数。

通用智能指针
std::shared_ptr<T>,强在可以指向任意对象,缺点也由此而生:由于引用计数保存在 shared_ptr 中,因此对智能指针的赋值操作是线程不安全的。这个问题,有一篇Blog论述,我觉得写得不错,就直接引用不细讲了。从原理和测试数据来看,我认为这篇 Blog 是靠谱的。

所以,虽然 shared_ptr 很强大,但使用场合需要注意:单线程随便用。多线程下,赋值过程要注意。单对单没啥问题,最好不要出现左值右值交叉的情况(一个线程在 A = B,另一个线程在 B = C)。若因业务需求无法避免的话,要考虑当作临界资源加锁保护。实在不行,就写一个专用智能指针,把引用计数放在 T 里面,加锁保护,就不会有问题了。

正则表达式
与散列表类似,不再需要第三方实现,现在 C++11 也直接支持正则表达式了。我以前要找一个 Unicode 支持得好的 Regex 库真的是苦水一堆,现在有了官方支持真的是太好了。

2017-05-23

招行网银在 Win10 下糟糕的支付体验

我这人不用 QQ,不用微信,微博借王维林也成功销号。然而,支付宝因为早年上淘宝购过物,搞过那个什么实名认证,我评估后认为不用比用下去可能更不安全,所以账号勉强留了下来。不过我也给自己定下了两条规矩:
  1. 不绑定银行卡的快捷支付。
  2. 不在里面保留超过 200 元人民币的余额(含余额宝)。
这两条规矩,使得我的支付宝平时基本上当作零钱包来使用,正式的支付只用在 PC + 网银上。这样,即使失窃,损失也很有限。再说,花钱方便是方便什么?方便你「败家」而已嘛。

因为我自己定的这些规矩违反某些方面的利益,所以经常给我形成一些耐性上的挑战。昨天我就又遇到了一次,来自招行网银 + Win10。
没有足够的余额,又不开快捷支付的话,在支付宝上支付一笔淘宝订单就只能通过网银了。我以前一直用招行网银,虽然它对 Chrome 和 x64 很不友好,但我还是耐着性子开 x86 IE 来在这种j「特殊场合」将就一下。反正也就是最后一步嘛,登录 Alipay 集中支付一下就好。但昨天在 Win10 机器上我又发现了新的问题。

Edge 不支持
Edge 登录支付宝是挺顺利的,但招行网银的页面一打开,直接说这是「使用陈旧技术的页面」,估计是因为 ActiveX。要想跨 Browser 提交表单估计还是做不到的,所以最后还是只有在 Win10 下打开 Internet Explorer。

无管理员权限登录会失败
IE 下几经折腾,可以打开招行的网银专业版了。然而插入 UKey 之后一登录就跳出 Dialog 让我输 Key 的密码。在确认不是钓鱼软件的情况下,连输四次后,专业版登录界面弹出提示说证书签名有问题云云。
我估计这问题就是因为权限不到位,果然给了管理员权限后就能正常登录了。然而专业版登录界面是从 IE 页面上 Call 出来的,这就意味着我得给 IE 管理员权限才行。这样的话风险就有点高,还好可以手动做单次授权。

以前我一直觉得,金融行业在 IT 技术应用上普遍保守,而在国内放眼看来,招行在技术方面还算是相对进取的。现在看来情况已经反转了。x64 都普及多久了?IE 份额离开绝对优势地位都多久了?Win10 正式版都上市多久了?到现在还在用着 ActiveX。招行里面搞技术的人这些年都不知道在干啥。曾经引以为傲的服务水平也许还没下滑,但也绝对不算突出了。我是不是该换一家主力银行了?

2017-05-22

VMware + Ubuntu 声卡失效事件

在公司和在家里,都用 VMware 各自安装了一台 Ubuntu 14.04 LTS 来玩。在家里的一台用得没有什么问题。在公司的那一台,周末打算加班的时候装个网易云音乐来听歌的时候,发现没有声音,才注意到 VMware 上有一条报错提示:
使用的设备标识号已超出本地系统范围。 声音将中断。
公司电脑上没有接音箱,所以以前曾经禁用过宿主机的 Windows Audio 服务。我以为是这个原因,去看了一下,Windows Audio 服务现在是启用中。把宿主机重启过,故障依旧。于是循例开始 Google。

Google 上搜到的中文内容,主要分为两派。一派说把 pulseaudio 卸载了就好了。我半信半疑地 apt-get remove pulseaudio 之后,嘿,还真的可以播放出声音了。不过更大的问题来了:系统设置丢了好多图标。回想起 apt-get 提醒我说要卸载掉的东西有一大堆,看来依赖于 pulseaudio 的东西不少。这条路应该不是什么正路。真是还好 VMware 有快照。

另一派说把宿主机上的立体声混音设备启用,故障就解决了。附和的人不少,看来有不少人都是这种办法解决的。具体页面有很多,随随便便就能搜到,我就不给出了。然而当我按照附带截图的操作指南去做的时候,问题又来了:我根本没有立体声混音设备。

这是怎么回事?我这人也不习惯卖关子。要说还是英文信息有用。英文页面上也有少数几个人抱怨遇到与我同样的问题(中文页面上我没有看到过)。最后还是 VMware 官方社区给出了有用的 解答

要简单解释一下的话,其实就是这么一回事:我这个宿主机上的 Win7 当初装起来之后,偷懒没有安装 Realtek 官方的声卡驱动程序,而是直接 Windows Update 安装了微软给出的驱动。估计微软的这个驱动是个阉割版,缺一些东西,装了之后虽然使用起来没什么问题,但像我这次遇到的什么立体声混音设备,大概就是被阉割掉的内容之一。所以 VMware 找不到指定的设备,于是就没法让 Ubuntu 中的声音设备正常工作了。

总之,按照 VMware 官方解答的指引,我去 Realtek 官网上下载并安装了声卡驱动,现在 VMware 里面的 Ubuntu 可以欢快地播放音乐了。

2017-05-21

正确地获取 Windows 的版本号

以前,想要获取 Windows 的版本号很简单,有个 Win32 API 函数名字叫做 GetVersion,望文生义,接下来要做的事情就是去 MSDN 上查下用法就可以了。

现在,GetVersion 会被报告成「过期函数」了。也许还能用,但(据 MSDN 说)起码在 Win10 上是别指望得到预期的结果了。道理也很简单,Win10 都搞滚动升级了,版本号规则肯定也和之前不一样了,你还指望这么老的函数能兼容么?

别痴心妄想了,GetVersionEx 也一样过期。那么,眼下有什么好办法吗?

一般来说,拿 Windows 版本号可能有两种用途:

  • 我想看看你 Windows 版本达到我要求没。
  • 我就是想知道你 Windows 版本号是多少。

对于前者,微软现在在 MSDN 上是这样推荐的:它做了一组 Version Helper functions,你如果想知道当前 Windows 的版本是不是某个特定的发行版,调这组函数就可以。我们来看看这组函数中三个典型:

  • IsWindowsXPOrGreater
  • IsWindowsXPSP3OrGreater
  • IsWindowsServer

不需要更多说明,我们从名字中就可以看出,这组函数可以用于判断 Windows 的大版本,Service Pack 的版本(结合大版本),以及能知道是不是服务器版操作系统。通常情况下,这些函数大概是够了。

但是,有的时候我们并不关心版本号高低,我们只是想要一个版本号(例如记录日志时)而已。微软对此的建议是:用 GetFileVersionInfo 去获取一个系统 DLL(例如 Kernel32.dll)的文件版本号(原文看 这里)。

相关的代码虽然能找到,MSDN 上也有官方例子(有点小 Bug),但比起一行 GetVersion 来代码量实在是不能算很少。由此可见,处理「过期函数」真的没有想象中那么容易。最后我还是提供一下我从项目代码中挖出来的一个实现吧。别照抄,如果你不想引入 STL 的话:

#include <windows.h>
#include <Strsafe.h>

#pragma comment(lib, "Version.lib")

// 获取文件版本
std::wstring GetFileVersionString(const std::wstring& strFilePath, bool bStrVer = false) {
    DWORD dwVerInfoSize = GetFileVersionInfoSize(strFilePath.c_str(), nullptr);
    if (dwVerInfoSize) {
        std::vector<BYTE> vecVerData(dwVerInfoSize);
        if (GetFileVersionInfo(strFilePath.c_str(), NULL, dwVerInfoSize, &vecVerData[0])) {
            LPCVOID pBlock = &vecVerData[0];

            UINT cbTranslate;
            TCHAR SubBlock[MAX_PATH];
            struct LANGANDCODEPAGE {
                WORD wLanguage;
                WORD wCodePage;
            } *lpTranslate;

            // 阅读语言和代码页列表
            VerQueryValue(pBlock,
                L"\\VarFileInfo\\Translation",
                (LPVOID*)&lpTranslate,
                &cbTranslate);

            if (bStrVer && lpTranslate) {
                // 读取第一种语言和代码页的文件版本
                for (size_t i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); ++i) {
                    StringCchPrintf(SubBlock, sizeof(SubBlock) / sizeof(TCHAR),
                        L"\\StringFileInfo\\%04x%04x\\ProductVersion",
                        lpTranslate[i].wLanguage,
                        lpTranslate[i].wCodePage);

                    LPVOID lpBuffer = nullptr;
                    UINT dwBytes;
                    if (VerQueryValue(pBlock, SubBlock, &lpBuffer, &dwBytes) && lpBuffer && dwBytes > 0) {
                        std::wstring strVersion(reinterpret_cast<TCHAR*>(lpBuffer));
                        return strVersion;
                    }
                }
            }

            // 未找到任何字符串版本
            VS_FIXEDFILEINFO* lpffi = nullptr;
            UINT uLen = 0;
            // 注意:这里的第二个参数 "\" 是固定写法,表示查询根块
            if (VerQueryValue(pBlock, L"\\", (LPVOID*)&lpffi, &uLen) && lpffi && uLen >= sizeof(VS_FIXEDFILEINFO)) {
                std::wstringstream wos;
                wos << HIWORD(lpffi->dwFileVersionMS) << L"." << LOWORD(lpffi->dwFileVersionMS) << L"."
                    << HIWORD(lpffi->dwFileVersionLS) << L"." << LOWORD(lpffi->dwFileVersionLS);
                return wos.str();
            }
        }
    }
    return L"";
}

// 获取 OS 版本信息
std::wstring GetOSVersion(const std::wstring& strWinSysDir, bool bStrVer) {
    std::wstring strWinSysFilePath = strWinSysDir;
    if (!strWinSysFilePath.empty() && strWinSysFilePath.back() != L'\\') {
        strWinSysFilePath += L'\\';
    }
    return GetFileVersionString(strWinSysFilePath + L"Kernel32.dll", bStrVer);
}

2017-05-20

SDL 检查不报错事件调查报告

Visual Studio 2013 VC 项目默认是启用 SDL 检查的。通常而言,这会使得一些「过期」函数在编译时被报告 Error。比如 strcpy 和 inet_addr 之类都会遇到这个问题。

理论上讲,这些过期函数的确不安全,或者说容易被不安全地调用。微软也很「贴心」地在编译器的报错信息中给出了解决方案,比如用 strcpy_s 和 InetPton 来替换,都不用你去搜解决办法了。所以按照我的习惯,一般是就地解决掉这些问题再往下走。

不过呢,可能有的人性子比较急,也有的时候是从旧项目移植,想快点编译完先跑一下看看效果。改代码毕竟要时间,从 strcpy 改成 strcpy_s 可能还好,而从 inet_addr 改到 InetPton 就真的没有想象中那么轻松。所以编译器也给出了另一种建议,你可以设上几个 Macro,SDL 也是可以被忽略的。

到此为止都还比较和谐,大家都是有商有量地做事情。然而当我腾出时间准备把过期函数扫扫干净,把同事临时加的 Macro 去掉之后一编译,问题来了——SDL 这次怎么不报错了?

再三确认过 vcxproj 中已经没有了相关的 Macro,并且 SDL 的确是打开了。但这次编译就是不会报错,似乎对我面前的 strcpy 视而不见。为什么呢?

Google 上搜了一大圈也没有方向。最后还是被我排除法硬试出来的——平台工具集如果选了 v120_xp,那么 SDL 即使打开,有些过期函数也不会报错。是不是 SDL 就此失效,我不清楚,因为我没法去覆盖所有的过期函数。我估计,微软是这样想的:你既然打算让这个程序跑在过期的 OS 上,那函数过期不过期已经不重要了。

其实还有一个更重要的原因:strcpy_s 还好,但要是把 inet_addr 真的换成了 InetPton,你就会发现在 WinXP 下你的程序根本就跑不起来。实际上,WinXP 就不支持 InetPton。MSDN 上的信息表明,最低也要 Vista 才可以。

我们的程序暂时还不能抛弃 WinXP 用户,但若要完全不知道用了哪些过期函数我又心有不甘,于是我打算做一个 #if……#else……#endif 来解决这个过期 OS 兼容的问题。搜了一下,正确的姿势应该是:
#if (_WIN32_WINNT >= 0x602)
#else
#endif
最后我是在 Debug 版本上用了平台工具集 v120,在 Release 时还是用了 v120_xp。过期函数只会局限在以上 Macro 范围内,有限度地使用。

另外,WinXP 也不支持条件变量 CONDITION_VARIABLE,所以这个服役期超长的操作系统是真的应该淘汰了。我不得不说一句:WannaCry,干得好!

2017-05-19

世界都在发展,而我们在翻墙

Google I/O 2017,没有熬夜看,只是大致了解了一下主要内容。
一转眼,Google+ 上线已经快六年,这六年中,热点变了好多次。SNS 貌似早已过气,大家都在纷纷议论 Google+ 到底什么时候被砍掉。曾经的热点 Android 现在已经变成波澜不惊的东西。Glass 悄声无息已经很久。无人驾驶汽车现在好多家都在搞。Assistant,Allo,Google 现在重点关注的是人工智能了。
也许 SkyNet 真的会出现,也许 Terminator 很快就会到来。我倒不太在意「人类被消灭」这种事情。如果能够见证这一时刻,也是一件幸事。

然而!眼看着世界都在发展,眼看着人类朝着技术奇点越走越近,我们在干什么?我们还在努力翻着墙,网络翻墙,肉体翻墙,精神翻墙,就在下下个周日。

美团那个 HR,不是歧视,只是蠢

最近有个「美团招聘」事件在网上发酵。孤陋寡闻的我今天才知道。众说纷纭,我也来发表一下意见。

------------------------------------------分隔线------------------------------------------

这位据说来自美团的 HR 抛出了一个「五不要」的招聘原则,被人发在了网上:
  1. 不要简历丑的;
  2. 不要研究生博士生;
  3. 不要开大众的;
  4. 不要信中医的;
  5. 不要黄泛区及东北人士。
美团说已经把这个人辞退了。简单关注了一下,不少评论意见很简单:要不说这人说得有道理,要不说这人脑残搞歧视。互联网信息快餐时代,简单的意见的确比较容易表述,也比较容易挑口水,所以我觉得我还是写个 Blog 说一下自己的意见。

HR 的招聘原则,脑子能想,但不能说或写出来。一旦别人知道了,那就是「歧视」。我觉得这个没啥大问题,不过我不关注这个层面。如果原则正确合理,即使是写了出来,我也觉得没啥问题。但是这「五不要」到底有没有道理,我觉得一定要分开来一条一条地看。如果你要么全盘接受,要么全盘否定,那么有可能脑子不太好使,请去医院挂号。

不要简历丑的
「丑」这个词,很主观。所以这个 HR 这样写出来,脑子不会太好使。主观的形容词你只能自己用,给别人讲了也无意义。你心中的「丑」跟别人心中的「丑」肯定不是同一回事。
那么,这一条有没有道理呢?当然有道理。招聘的考察因素当然可以包括被招聘者的审美和美术功底,包括排版能力。就算不是相关岗位要求的技能,但「审美观」作为对一个人综合素质的要求是并没有什么问题的。这一条,道理上没问题,只是不能写出来,因为写了也没意义。

不要研究生博士生
首先这一条写得有点问题,如果写成「不要研究生及其以上学历者」或「只要本科(含)以下学历者」可能更合适。不过我也不想抠这种文字上的细节。单看这一条表述的意思,一点问题也没有。招聘条件中对于学历的要求,这个能有什么问题?也许其他人觉得 HR 是歧视高学历者,但也可能是岗位不需要,或简单的「雇不起」。你要是看出来里面有「歧视」,那其实只说明你心里才有「歧视」。

不要开大众的
我初看到这一条时感觉简直是「这人神经病」。「开大众的」怎么着你了?撞你了还是蹭你车了?上网一搜才知道所谓「神车」是啥意思。
那么我们现在知道 HR 是什么意思了。不过,这一条问题很大。HR 不想要具备某种价值观的人,这个很容易理解。但是开个大众车就一定具有某种价值观吗?可以举出反例的情况太多了。后面的分析会告诉我们,这个 HR 一定不是一个好的理科生,他的逻辑学连基础都没有。

不要信中医的
这一条没有问题。信中医的会有什么价值观,我们还是很清楚的。HR 不想要有这种价值观的人,那是他的自由,一点问题也没有。

不要黄泛区及东北人士
看到这一条我又糊涂了。什么是「黄泛区」?黄皮肤泛滥?三藩还是雪梨?查了一下才知道说的是河南那片儿。不过总之「地图炮」仨字跑不掉。
这一题也跟第三条一样,问题很大。你一个 HR 招个白领而已,要考察的无非主要就是能力和性格。这两样哪个跟地区有必然的关系?

------------------------------------------分隔线------------------------------------------

好了,现在我们来看看这位 HR 犯了什么毛病?
他的问题不是「歧视」。他是脑残,是逻辑没学好。他不知道什么是充分条件,什么是必要条件。我估计他中学时平面几何的分数一定很低。

招人的时候,你若是 HR,一定会提一堆的条件。这些条件里面,有一些是你要求应聘者必须满足的,不具备的话就别来了,来了也没用。这种叫做「必要条件」:若要应聘我司,必须如何如何。通常这类条件都会是「白名单」形式,符合这类条件,你只是有了资格,但并不保证一定会被聘用,也不保证不会被其它条件给刷掉。
还有一类条件,如果你符合了,你就别来了,不会考虑你的。排除性的,也就是上面提到的「XX 不要」。这类条件,叫做「充分条件」:你只要如何如何,我就一定不要你。注意,这里一定是「黑名单」性质。除了骗子公司,没有 HR 会说「只要你符合 XX 条件,我就一定要你」。全世界都没有,不然这家公司早倒闭了。

黑名单有效的前提,是该条件可以充分覆盖 HR 希望避免的区域。换句话说,你排除掉的,一定是你不想要的。你的条件和你要的结果,中间要有逻辑关联性。举个例子:你不喜欢染头发的员工,你就列一条「不得染发」。你要列一条「必须是黑发」,那就有问题了。因为你这个条件涉及到了那些天然非黑发者,却并没有排除掉「染成黑发」者。结果就是,从正反两个方面,你想要的效果都没有达到。我们就会说你这个条件订得有点脑残。
美团这个 HR 的第三条和第五条就是如此。开大众的也许有价值观不符的,但也有跟价值观无关的。价值观不符合你要求的人,自然也有开其它品牌车的。「黄泛区」和东北人士可能有很多人你不喜欢,但这么多人你能确定所有人都不对你胃口?其它地区的人就都对你胃口?
所以啊,列出了这两条惹口水的条件,却既不能完全排除你不想要的人,还可能「误伤」本来符合你条件的人。站在公司立场看来,这种HR完全就是在瞎鸡巴搞,本职工作显然不合格,被辞退掉完全是活该。站在我们旁观者的立场,歧视归歧视,要是歧视得有道理,比如「不招共产党员」,我们都会点头称是。你要是说「不招关注过郭文贵者」,我们都会骂你脑残欠抽。

少年们,无论将来干什么工作,请先学好逻辑!

2017-05-18

当 Win7 Windows Update 遭遇 0x80073712

Windows Update 一直以来都以会遇到各种 Error 代码而闻名。今天又遇到一例,记录一下。

起因是 WannaCry。我有一堆各种 OS 版本的虚拟机,其中一台 Windows7 SP1 x86 使用得很不频繁,昨天打开一看,上次 Windows Update 已经是 2016 年 09 月的事情了。虽然 NAT 挡在宿主机后面其实不会有啥问题,但是按照我的习惯,下班前还是让它去打了补丁。曾经在上一家公司的遭遇一直在提醒我:有人的虚拟机中了震荡波,然后不知情的时候被做了快照,于是每隔一段时间测试机房就会忙活一阵子(测试机为了测试程序的补丁管理功能是不打补丁的)。

今天早上一来,红色儿的,4 个成功 2 个失败。我也没太放在心上,公司网络有时候会断,说不定是下载失败。再来了一次,在下载到 11% 的时候又失败了。我把 VPN 开起来(曾经有不开 VPN 打补丁会下载失败的经历),上了个厕所回来,然而这次还是失败,我看了下 ErrorCode:80073712。每次都是这个。好吧,开始 Google

官方网页 推荐的做法大概是这样的:对于 Win7 而言,首先请先尝试用 SFC 修复一下。如果还不行,那么请下载工具 System Update Readiness tool 进行修复。

SFC 这货其实没啥鸟用,反正我每次用都没啥好结果。这次也不例外,扫描到 44% 时告诉我:虽然我们发现有错,但是无法修复,你去看日志吧。

试了下再次 Windows Update,还是报 0x80073712。好吧,只好试试看那个修复工具了。下载下来两百多 MB,安装了老半天。再次 Windows Update,这回进度开始超过 11% 了,我长舒一口气。终于 OK 了。

顺便瞄了一眼同页面上对 WinXP 的问题处理建议,仅仅提到 SFC。看来真的是该放弃这破烂了。