善意提醒
2017-12-27
Linux关机权限的特殊情况
正常想来没错:shutdown/reboot如果可以不用root权限那还了得?然而其实至少在RHEL 7.2上并不绝对是这样子。当出现以下的特殊情况时:
1.是当前唯一登录的用户;
2.直接调用reboot,或shutdown带了now参数;
3.登录会话来自本地物理终端。
同时满足以上所有条件的话,就可以无需root权限以任何用户的身份关机或重启服务器。
我用RHEL 7.2默认安装的「基础设施服务器」进行的测试。Debian 9没有这个问题。我想这样的做法大概思路是:「如果你都摸到物理服务器边上,急着要马上关机,并且我认为这不会影响到其他人,那么当然可以让你关机,因为就算不让你关机你也可以拔电源线对吧?」
当然,这种想法应该没有考虑到服务器上运行爬虫之类的情况。我没有做更多测试,也许如果有一个别人的daemon进程就会使得结果不一样。不过我想这个事情可以提醒我们的是:Linux的事情不要想当然,也不要网上说什么都信。不要把一切都交给系统默认安全性设置,对于普通用户还是乖乖地把权限控制严格点比较好。
2017-06-22
防贼不编年史
Chapter One
很早的时候,赛博世界还只有病毒,没有贼。
那时候,有的病毒还可以和人类和睦相处,称之为「良性病毒」。
我有一个游戏,从别人那边复制过来的时候,就是带毒的。文件型病毒,文件体被加了密,还给搬到了隐藏扇区。不过游戏本身挺有意思,The Incridible Machine,第一代。于是我每次就把 BIOS 里的硬盘给 Disable 掉,然后再玩。慢虽然慢点,但只要记得玩过之后重启,就不会有什么问题。
Chapter Two
尽管兜里没什么钱,但 Internet 时代还是到来了。
网管差点把我从网吧赶了出去。因为他终于「逮到」我在用 SuperRabbit。他已经连续好几天都把时间花在恢复系统设置上了。我想日后 硬盘还原卡 卖得这么好,其中应该有我一份功劳。
「机器狗」真不是我放的。
Chapter Three
CIH 爆发了。
买了刷 ROM 机器的人都发了笔小财。
其他人:哇,没想到病毒也可以这么凶残!
我倒是没什么感觉,因为我那个时候还在用着一块 80486。
Chapter Four
IDT-C6 的发热好低,主频超到 100MHz 也不用风扇。
哇,有好多同学都安装了 冰河 哎!
咦,这位同学在玩美少女梦工场 3 呢。我只有 2。
「同学你好,你的游戏可以也 copy 给我一份吗?哎哎,不要关机!……」
Chapter Five
不知道从什么时候开始……
几乎所有的 IT 公司,都以在你的 IE 上装一个插件或工具栏为荣。
几乎所有的安装包,都会附带一两种「小东东」。
刚开始我还真没太放在心上,甚至对某些还持欢迎态度。然后,我的机器越来越慢了。
做毕业设计时,我接触到了 ActiveX。
Chapter Six
工作了。从 K6-2 一下子跳到 Pentium 4 的感觉真好。
「见鬼,这浏览器上什么时候多了这么些鬼东西?上网助手?中文实名?卸掉!就是它搞得我们的 OCX 不能用了。」
「你要记得安装 Windows
2000 SP4 补丁,不然我们的程序用不了。」
对了,有个新软件叫 VMware,真好玩!
Chapter Seven
我开始小心翼翼地上网浏览,对于弹出的 OCX 安装提示统统点「否」,后面视情况而定。因为我知道一旦安装了之后别人能做些什么事情。然而,有些人我是注定帮不了他们:
我:「你的 IE 上装这么多工具栏干什么?」
A:「不然我怎么上网?」
我:「Google 的网址是三达不溜点……」
A:「别说了我记不住。」
愿上帝保佑他们,阿门。
Chapter Eight
换工作了,当小头头了,总算有双核电脑用了。VMware 可以有自己单独的 CPU 了。
真不敢相信,以前在一台 768MB 的 P4 笔记本上跑了一个 Oracle8i + Lotus Domino
R5 + 两个 Resin + Word + 若干 IE,去招标现场做演示的时候,机器到底是怎么撑下来的。
借着新工作接触到了 IceSword。妖魔鬼怪你们都现出原形罢!
测试人员:不好了,机房又爆发「震荡波」了。
Chapter Nine
我开始仔仔细细地打补丁,开启 Windows Update,总是保持自动更新。
而很多同事都是直接关掉了事:「工作到一半老是跳出来叫我更新,太烦!」
愿上帝保佑他们,阿门。
Chapter Ten
我发现同事们总算愿意给机器打补丁了。
同事:这个东西叫 360,打补丁蛮快的。
我:我也来试试。咦,这个 KB360018 怎么这么奇怪?
Chapter Eleven
不知道从什么时候开始,软件安装开始必须得小心了。
因为一不小心你就会安装上好几个不请自来的软件,电脑上会变成软件博物馆。送来给我「修一下」的电脑无一不是如此。连我自己有时候不小心也会中招。
有人建议我把家里电脑换成 Linux,不过我觉得对父母吩咐到位了,一般还是没事。毕竟我有 TeamViewer。
Chapter Twelve
不知道从什么时候开始,国产软件慢慢地不能用了。
其实不是不能用,是不敢用了。因为开始流行一种东西叫做「全家桶」。腾讯、百度、迅雷、阿里、360、金山……。所有的这些曾经为我服务过的软件,仿佛都得了癌症。你不知道他们在背后干些什么,但是你的电脑的确越来越慢,行为越来越不正常了,而且你的 hosts 总是失效得很快。于是干脆就不用了。
不用了,然后也并没有什么事情发生。并没有像有的人以为的那样会社会大乱,民不聊生。人民照样活得很好。
Chapter Thirteen
一夜之间,我的密码就不再是「我」的密码了。
许多年前,我自己写的同学录,为了避免被 SQL injection,就把服务端存储的密码改成了 MD5 Hash。
后来,知道了世界上有种东西叫做「彩虹表」,于是我学会了 salt。
再后来,我看到了 王小云教授 的论文,于是我 Hash 算法至少会是 SHA256。
然而,这么多年过去了,这帮狗日的居然还在服务器上存 明文密码。谁要硬说这里面没有阴谋,我只能说你的心挺大。
Chapter Fourteen
没想到,我的 QQ 也被盗了!
我已经很多年没用过 QQ 了。但是因为我太太的手机上有游戏用我的 QQ 账号登录着,所以我觉得企鹅还不敢回收我的账号。
突然间它就变了一个名字,列表里的好友也变成了一堆海南人。我还能再登进去,密码并没有被改。所以我一直到现在也没想通是怎么回事。
我把个人状态改成了「明文密码好」,然后就把这个 QQ 号扔那里了,就像它从未存在过。
Chapter Fifteen
那个周末,当我还在外地顶着紫外线用流量上网的时候,全世界有许多人已经 想哭 了。
贼要的是 Bitcoin。
当第二天 IT 问我笔记本装没装补丁的时候,我微笑着告诉他「Linux」。
不过我马上又想起来 Heartbleed 的事情我还没处理,于是我又有点笑不出来了。
我也想要别人的 Bitcoin。
2017-06-13
银联钱包你真垃圾,我一点都不欢迎你
2017-05-31
淘宝天猫都碰不得啊
六一儿童节快到了,给儿子买点什么礼物好呢?虽然前不久刚买了一架UH-60和一把Glock17,但我还是想买点对儿童稍微有点教育意义的东西。思来想去,觉得地球仪不错。正好暑假也准备带家人出国去转转。若是连自己去了哪里却一点概念都没有,那岂不是很遗憾?
其实以前我是很吃过几次亏的,所以也曾经痛下决心告诉自己大部分东西都不能在淘宝上买了。但前不久那次买玩具的感受实在是不错:我擦!居然有金属的Dragunov?这次一翻地球仪,我更是震惊了:我擦!居然还有磁悬浮的?!
糟糕
虽然买之前看过一些评论,算是心里有所准备,但还是没想到这么麻烦。送的那个什么「悬浮棒」根本就不好使。还是有个评论里面说用中性笔的办法比较管用。好不容易能够比较顺利地「悬浮」上了,然而只要有一点点扰动,平衡立马就被打破,通常的结局就是地球仪被「铛」的一声吸到顶上。而且就算是很小心地退开,一般也坚持不了几分钟。我发现这大概跟加工精度有关系,因为顶上的磁体是斜的,而我无法去校正。
这它妈让人怎么用?我不可能每天都花一两个小时的时间在折腾这东西上。睡觉时还不能开着,不然保管神经衰弱。要是只能摆着或吸到顶上,那几十块的普通地球仪岂不是更好?我干嘛买个这么贵的银样蜡枪头?
不行,退货!
退货
在淘宝上买东西,顺利的话一切都好。然而一旦买到糟糕的玩意儿需要退货,那麻烦就来了。我以前的糟糕的购物经历无一不源自于此,这次当然也不例外。当我以「质量问题」为原因要求退货时,卖家拒绝了。我对此也并不意外——反正最糟糕也就是哪样,总不可能直接投降吧。
卖家拒绝的理由看起来很好笑——产品有正规工厂专利进货发票。这话好眼熟啊!对了,我以前在某个论坛上看卖家们交流心得时提到过,如果被投诉三无产品,就这样应对,只要你拿得出发票来,淘宝小二可不管它是不是真的……。呵呵,这样的发票当然可以买。有些买的发票连税务局那边都能过,何况淘宝。淘宝的处理方式很简单,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/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);
角括号
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下糟糕的支付体验
- 不绑定银行卡的快捷支付。
- 不在里面保留超过200元人民币的余额(含余额宝)。
因为我自己定的这些规矩违反某些方面的利益,所以经常给我形成一些耐性上的挑战。昨天我就又遇到了一次,来自招行网银+Win10。
没有足够的余额,又不开快捷支付的话,在支付宝上支付一笔淘宝订单就只能通过网银了。我以前一直用招行网银,虽然它对Chrome和x64很不友好,但我还是耐着性子开x86 IE来在这种j「特殊场合」将就一下。反正也就是最后一步嘛,登录Alipay集中支付一下就好。但昨天在Win10机器上我又发现了新的问题。
Edge不支持
Edge登录支付宝是挺顺利的,但招行网银的页面一打开,直接说这是「使用陈旧技术的页面」,估计是因为ActiveX。要想跨Browser提交表单估计还是做不到的,所以最后还是只有在Win10下打开Internet Explorer。
无管理员权限登录会失败
IE下几经折腾,可以打开招行的网银专业版了。然而插入UKey之后一登录就跳出Dialog让我输Key的密码。在确认不是钓鱼软件的情况下,连输4次后,专业版登录界面弹出提示说证书签名有问题云云。
我估计这问题就是因为权限不到位,果然给了管理员权限后就能正常登录了。然而专业版登录界面是从IE页面上Call出来的,这就意味着我得给IE管理员权限才行。这样的话风险就有点高,还好可以手动做单次授权。
以前我一直觉得,金融行业在IT技术应用上普遍保守,而在国内放眼看来,招行在技术方面还算是相对进取的。现在看来情况已经反转了。x64都普及多久了?IE份额离开绝对优势地位都多久了?Win10正式版都上市多久了?到现在还在用着ActiveX。招行里面搞技术的人这些年都不知道在干啥。曾经引以为傲的服务水平也许还没下滑,但也绝对不算突出了。我是不是该换一家主力银行了?
2017-05-22
VMware+Ubuntu声卡失效事件
使用的设备标识号已超出本地系统范围。 声音将中断。公司电脑上没有接音箱,所以以前曾经禁用过宿主机的Windows Audio服务。我以为是这个原因,去看了一下,Windows Audio服务现在是启用中。把宿主机重启过,故障依旧。于是循例开始Google。
Google上搜到的中文内容,主要分为两派。一派说把pulseaudio卸载了就好了。我半信半疑地apt-get remove pulseaudio之后,嘿,还真的可以播放出声音了。不过更大的问题来了:系统设置丢了好多图标。回想起apt-get提醒我说要卸载掉的东西有一大堆,看来依赖于pulseaudio的东西不少。这条路应该不是什么正路。真是还好VMware有快照。
另一派说把宿主机上的立体声混音设备启用,故障就解决了。附和的人不少,看来有不少人都是这种办法解决的。具体页面有很多,随随便便就能搜到,我就不给出了。然而当我按照附带截图的操作指南去做的时候,问题又来了:我根本没有立体声混音设备。
这是怎么回事?我这人也不习惯卖关子。要说还是英文信息有用。英文页面上也有少数几个人抱怨遇到与我同样的问题(中文页面上我没有看到过)。最后还是VMware官方社区给出了有用的解答。
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检查不报错事件调查报告
理论上讲,这些过期函数的确不安全,或者说容易被不安全地调用。微软也很“贴心”地在编译器的报错信息中给出了解决方案,比如用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)最后我是在Debug版本上用了平台工具集v120,在Release时还是用了v120_xp。过期函数只会局限在以上Macro范围内,有限度地使用。
#else
#endif
另外,WinXP也不支持条件变量CONDITION_VARIABLE,所以这个服役期超长的操作系统是真的应该淘汰了。我不得不说一句:WannaCry,干得好!
2017-05-19
世界都在发展,而我们在翻墙
一转眼,Google+上线已经快六年,这六年中,热点变了好多次。SNS貌似早已过气,大家都在纷纷议论Google+到底什么时候被砍掉。曾经的热点Android现在已经变成波澜不惊的东西。Glass悄声无息已经很久。无人驾驶汽车现在好多家都在搞。Assistant,Allo,Google现在重点关注的是人工智能了。
也许SkyNet真的会出现,也许Terminator很快就会到来。我倒不太在意「人类被消灭」这种事情。如果能够见证这一时刻,也是一件幸事。
然而!眼看着世界都在发展,眼看着人类朝着技术奇点越走越近,我们在干什么?我们还在努力翻着墙,网络翻墙,肉体翻墙,精神翻墙,就在下下个周日。
美团那个HR,不是歧视,只是蠢
------------------------------------------分隔线------------------------------------------
- 不要简历丑的;
- 不要研究生博士生;
- 不要开大众的;
- 不要信中医的;
- 不要黄泛区及东北人士。
HR的招聘原则,脑子能想,但不能说或写出来。一旦别人知道了,那就是「歧视」。我觉得这个没啥大问题,不过我不关注这个层面。如果原则正确合理,即使是写了出来,我也觉得没啥问题。但是这「五不要」到底有没有道理,我觉得一定要分开来一条一条地看。如果你要么全盘接受,要么全盘否定,那么有可能脑子不太好使,请去医院挂号。
不要简历丑的
「丑」这个词,很主观。所以这个HR这样写出来,脑子不会太好使。主观的形容词你只能自己用,给别人讲了也无意义。你心中的「丑」跟别人心中的「丑」肯定不是同一回事。
那么,这一条有没有道理呢?当然有道理。招聘的考察因素当然可以包括被招聘者的审美和美术功底,包括排版能力。就算不是相关岗位要求的技能,但「审美观」作为对一个人综合素质的要求是并没有什么问题的。这一条,道理上没问题,只是不能写出来,因为写了也没意义。
不要研究生博士生
首先这一条写得有点问题,如果写成「不要研究生及其以上学历者」或「只要本科(含)以下学历者」可能更合适。不过我也不想抠这种文字上的细节。单看这一条表述的意思,一点问题也没有。招聘条件中对于学历的要求,这个能有什么问题?也许其他人觉得HR是歧视高学历者,但也可能是岗位不需要,或简单的「雇不起」。你要是看出来里面有「歧视」,那其实只说明你心里才有「歧视」。
不要开大众的
我初看到这一条时感觉简直是「这人神经病」。「开大众的」怎么着你了?撞你了还是蹭你车了?上网一搜才知道所谓「神车」是啥意思。
那么我们现在知道HR是什么意思了。不过,这一条问题很大。HR不想要具备某种价值观的人,这个很容易理解。但是开个大众车就一定具有某种价值观吗?可以举出反例的情况太多了。后面的分析会告诉我们,这个HR一定不是一个好的理科生,他的逻辑学连基础都没有。
不要信中医的
这一条没有问题。信中医的会有什么价值观,我们还是很清楚的。HR不想要有这种价值观的人,那是他的自由,一点问题也没有。
不要黄泛区及东北人士
看到这一条我又糊涂了。什么是「黄泛区」?黄皮肤泛滥?三藩还是雪梨?查了一下才知道说的是河南那片儿。不过总之「地图炮」仨字跑不掉。
这一题也跟第三条一样,问题很大。你一个HR招个白领而已,要考察的无非主要就是能力和性格。这两样哪个跟地区有必然的关系?
------------------------------------------分隔线------------------------------------------
好了,现在我们来看看这位HR犯了什么毛病?
他的问题不是「歧视」。他是脑残,是逻辑没学好。他不知道什么是充分条件,什么是必要条件。我估计他中学时平面几何的分数一定很低。
招人的时候,你若是HR,一定会提一堆的条件。这些条件里面,有一些是你要求应聘者必须满足的,不具备的话就别来了,来了也没用。这种叫做「必要条件」:若要应聘我司,必须如何如何。通常这类条件都会是「白名单」形式,符合这类条件,你只是有了资格,但并不保证一定会被聘用,也不保证不会被其它条件给刷掉。
还有一类条件,如果你符合了,你就别来了,不会考虑你的。排除性的,也就是上面提到的「XX不要」。这类条件,叫做「充分条件」:你只要如何如何,我就一定不要你。注意,这里一定是「黑名单」性质。除了骗子公司,没有HR会说「只要你符合XX条件,我就一定要你」。全世界都没有,不然这家公司早倒闭了。
黑名单有效的前提,是该条件可以充分覆盖HR希望避免的区域。换句话说,你排除掉的,一定是你不想要的。你的条件和你要的结果,中间要有逻辑关联性。举个例子:你不喜欢染头发的员工,你就列一条「不得染发」。你要列一条“必须是黑发”,那就有问题了。因为你这个条件涉及到了那些天然非黑发者,却并没有排除掉「染成黑发」者。结果就是,从正反两个方面,你想要的效果都没有达到。我们就会说你这个条件订得有点脑残。
美团这个HR的第三条和第五条就是如此。开大众的也许有价值观不符的,但也有跟价值观无关的。价值观不符合你要求的人,自然也有开其它品牌车的。「黄泛区」和东北人士可能有很多人你不喜欢,但这么多人你能确定所有人都不对你胃口?其它地区的人就都对你胃口?
所以啊,列出了这两条惹口水的条件,却既不能完全排除你不想要的人,还可能「误伤」本来符合你条件的人。站在公司立场看来,这种HR完全就是在瞎鸡巴搞,本职工作显然不合格,被辞退掉完全是活该。站在我们旁观者的立场,歧视归歧视,要是歧视得有道理,比如「不招共产党员」,我们都会点头称是。你要是说「不招关注过郭文贵者」,我们都会骂你脑残欠抽。
少年们,无论将来干什么工作,请先学好逻辑!
2017-05-18
当Win7 Windows Update 遭遇 0x80073712
起因是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。看来真的是该放弃这破烂了。
2017-03-29
对VS2013下C++11的精准转发与通用引用的一点研究
#include <iostream>
#include <string>
class A
{
public:
template <typename T>
void foo(T&& t)
{
T _t = std::forward<T>(t);
}
};
int main()
{
std::string s1 = "test";
A a;
a.foo(s1);
std::cout << "1:" << s1 << std::endl;
a.foo(std::move(s1));
std::cout << "2:" << s1 << std::endl;
a.foo("ok");
}
1:test 2:
template <typename T> class A { T _t; public: void foo(T&& t) { _t = std::forward<T>(t); } }; int main() { std::string s1 = "test"; A<std::string> a; a.foo(s1); std::cout << "1:" << s1 << std::endl; a.foo(std::move(s1)); std::cout << "2:" << s1 << std::endl; a.foo("ok"); }
error C2664: “void A<std::string>::foo(T&&)”: 无法将参数 1 从“std::string”转换为“std::string&&”
with
[
T=std::string
]
无法将左值绑定到右值引用
template <typename T>
class A
{
T _t;
public:
template <typename X>
void foo(X&& t)
{
_t = std::forward<X>(t);
}
};
template <typename T>
class A
{
T _t;
public:
template <typename X>
void foo(X&& t);
};
template <typename T>
template <typename X>
void A<T>::foo(X&& t)
{
_t = std::forward<X>(t);
}
2017-03-06
作死的TerminateThread
0x77B16BB9 (ntdll.dll) (XXX.dmp 中)处有未经处理的异常: 0xC0000005: 读取位置 0x1C7695E8 时发生访问冲突。我一般首先会去看CallStack,因为PDB都有,所以得到的情况很清晰:
ntdll.dll!RtlpWakeByAddress()这个调用栈看得我有点糊涂。这个RecMutex来自ICE的最新版本,我从1.3.0版就开始用ICE,用到现在的3.6.3。这种基础的代码我有信心不会有问题,也不会用出问题来。何况RecMutex只是对CRITICAL_SECTION的一个简单封装,代码并不多,也并不复杂。我这次在自己项目代码中用之前恰好看过一遍,也认为这里面不会有什么问题。
ntdll.dll!RtlpUnWaitCriticalSection()
ntdll.dll!_RtlLeaveCriticalSection@4()
XXX.dll!RecMutex::unlock()
XXX.dll!LockT<RecMutex>::~LockT<RecMutex>()
……
具体到RecMutex::unlock(),其实也就是干了这个事情:
void RecMutex::unlock() const
{
if (--_count == 0)
{
LeaveCriticalSection(&_mutex);
}
}
2017-02-03
折腾boost::python的一些收获
一、关于万能变量类型
对于习惯了Python的C++程序员而言,boost::python::object这个东西是一个巨大的诱惑。它让你几乎可以像在Python里面那样使用弱类型的变量,同时还支持数组和字典之类的复杂变量类型,并且还支持嵌套。这简直就是一个万能变量类型,有了它,常见的需求几乎都可以满足了。
而且它还快,还容易用。它其实是PyObject*的一个封装,也就是说PyObject*其实功能也一样,但是没它容易使用。JsonCPP里面的Json::Value也可以「万能」,但性能与boost::python::object相差颇多。这真的是一个巨大的诱惑。
但是在这里,我要给大多数C++程序员泼一盆冷水。在单线程下,这个梦想可能真的是事实:但是在多线程下,boost::python::object就是一个坑!
boost::python::object为什么快?因为它基于PyObject*,具有引用计数,所以赋值才飞快,浅拷贝嘛。但是引用计数的问题就是线程不安全。
当然,光是引用计数本身不会导致线程安全性问题,导致问题的是引用计数带来的临界区对象引用问题,而归根结底,是Python C API的「并发问题不归我管」的思路。boost::python的封装机制使得对象引用不好控制,也不太可见。想法是好的:使用者不需要关心这些。但现实就很无奈了。
所以,尽管很不甘心,关于万能变量类型的实现,我还是老老实实地用回了Json::Value。
二、类型转换与判断
要从boost::python::object转换成C/C++原生变量类型,一般用boost::python::extract<T>。转出来的对象调check()就可以确定是不是正确的类型。转boost::python::list或boost::python::dict也是一样。
有一点需要特别注意的是:用boost::python::extract<bool>的时候,很可能得不到你预想的结果。你会发现,int也被转成bool了(check()返回true),反过来也一样。这个不是Bug,是boost::python故意这样搞的。BOOST_PYTHON_BOOL_INT_STRICT宏可以解决这个问题(必须改动boost::python源代码并重新编译,因为相关代码在cpp里面不在头文件里面),它使得int和bool将被严格区分。
但是boost::python之所以把int和bool混起来用也不是没有道理的。这样使得MFC里面的那些BOOL(不是bool)类型的函数形参(和返回值)可以直通Python,封装起控件来尤其方便。如果你设了BOOST_PYTHON_BOOL_INT_STRICT,就不能这样干了,必须显式转换。左是一刀,右也是一刀。你们自己掂量吧。
反正我是没有去加BOOST_PYTHON_BOOL_INT_STRICT,而是自己通过_stricmp(value.ptr()->ob_type->tp_name, "bool")搞定的。
三、清空boost::python::list
用惯STL刚开始用boost::python的人可能会头大——怎么dict有clear()但list没有?
答案很简单,因为Python C API没提供,所以boost::python就没有。
说到底,boost::python就是对Python C API的一个封装而已。你如果去看boost::python的源代码,甚至会发现里面不少的「成员函数」其实是跑去执行了一句Python脚本。不了解细节的C++程序员很容易在这种地方栽跟头,所以我觉得boost::python绝对不会是一个终极形态。
要想清空数组,并不是完全没有办法。我找到的一个办法是boost::python::delitem(list, boost::python::slice())。并发调用时记得用本文最后一节的办法加锁。但是我是真的不建议在多线程环境下用boost::python,太多坑了。如果实在要用,在完成了Python脚本调用,把数据转换好之后,就赶紧离开boost::python区域吧。
四、boost::python::object深拷贝
boost::python::object默认的赋值操作都是作浅拷贝,极快。然而,有的时候需要深拷贝怎么办呢?
简单数据类型直接extract出来就是深拷贝(传值)了。会有问题的仅仅是复杂数据结构,具体地说,list和dict。
dict又是有提供copy(),理由还是——Python里面的dict有copy(),而list就没有。我尝试过最简单的办法,就是把一个list放到一个dict里面去copy()完再拿出来用。这样肯定可以达到目的。至于有没有更好的办法呢?我反正是自那之后就弃坑了,你们去研究吧。
五、多线程并发加锁
C++调Python的时候的加锁是一个不小的话题。不过这方面中文资料也算不少,有兴趣可以去搜来研究。我这里就简单地说一下跟boost::python相关的部分。
加锁是用PyGILState_STATE,像这样:
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
…… // 调用Python脚本
PyGILState_Release(gstate);



