20131222

冬天的温泉

冬天的温泉


本来以为今年不会再有机会了,结果接近年底的时候突然鼓起勇气跟各位领导老师请了假,去东汤疗养。

东汤是什么地方呢,如果我不说,可能你根本不会知道。很小的一个镇子,在辽宁省凤城市下辖。估计凤城之前你也没有听说过,是丹东下辖的一个市。丹东,在中朝边界;是老gada的媳妇的老家;我上初中还是高中的时候有一块丹东产的机械表,不记得是不是我做实验摔坏的那块了;当地人操辽东口音,跟通化人差不多。

坐一夜列车,从凤城下车,倒出租车。大清早,微有晨雾,大家都很兴奋。司机也很兴奋,一路告诉我们东汤是这样一个地方,"没有岗没有猴儿,一把瓜子嗑到头儿"。岗是指交通岗,猴是指站岗的人,一把瓜子嗑到头,极言小镇之小。张健此时问,是指只有一个十字路口吗?我说,不是,而是只有一条街,从头到尾。

司机还告诉我们,回家以后跟别人怎么说在东汤的生活,"每天一倒两泡三饱"。即每天都睡一觉,醒了早饭以后就泡温泉,然后吃中午饭,然后再泡,再吃晚饭,然后睡觉。我们加了点活动,在饭前还要买海鲜。原以为冬天就什么也没有了,只有粗茶淡饭,特意带了火腿肠。结果除了虾根本没有,别的还算全乎,蟹虽小,肉还算实成,蚬子和扇贝也饱满。海鲜店新开了一家,但是一直关着门,所以一直没能光顾。我们常去的那家海鲜店老板很喜欢我们,我们几乎每次包圆一个品种,到离开的时候,他所有的存货所剩无几。每天,在开饭的时间,周围一群老头老太太吃着素淡的东西,而我们一直海鲜海鲜,感觉自己很土豪的样子。

除了泡澡,安排了三次远足。一次是某家疗养院的后山,那家疗养院的名字忘了,就在镇中心,市场的旁边。那家能游泳,邦哥和鞠同学他们去看价格了,说是非常非常贵。那次爬山把大家冻够呛,第二次爬山的邦哥决定谁也不能带单反去了。事实上,第一次爬山鞠同学的单反也只起到iphone的作用。大家认定,如果主要以拍自己或互拍为主,她的根本不需要单反,只需要美图秀秀就行了。

今年东汤令我惊讶的第一个地方,就是没有雪。这在第一次爬山的时候表现出来,回来的时候满身都是灰。而同一时间的长春,在街道上积累了几场雪,都没来得及溶化。第二次爬山之前刚好下了微雪,把灰尘都盖住了,还能通过脚印判断,我们的路线人迹罕至。

第二次单程约2.5小时。我们重走了去年我和二猫妈差点迷路的路线的前半段,也就是有路的部分。后来,经过一个满是砍下来的柴火的山谷,一段稍微困难的坡路,我们爬到一个山脊上。在从山脊上露头以前,风平浪静的,一探头出来,对面的山风非常猛烈,站立困难。山脊上有几块一人多高的大石头,刚好挡风,能看到山下不远有小汪小湖。但是大家已经决定,决对没有体力下山到那里去了。从当时的照片上也能看出来,个个被风吹得或口眼歪斜,或表情凝重。

第三次只有我和二猫妈两个人,带着单反,单程也是约2.5小时。第二次爬山忘记开GPS记录了,也才发现已经一年多没有徒步了,已经忘掉开GPS这回事。从腰突以后,想起徒步就心生惧意,竟然到了这种程度。第三次的时候中徒开了GPS,记录了去和返程的路线,包括哪里过了马路,还有跨过大河轨迹。去年我独自行走这条路线的时候,二猫妈已经体力透支返回了,当时正夜雾升起,脚下也看不清楚,旁边和对面的山都在一片灰的模糊里黑越越的,到达大河的时候浑身冰冷,快透支的感觉。这一次是白天,有了上次的经验,心里也更有底,走到大河的时侯还没有觉得累。上一次,在一片黑暗里看到大河的白光闪着,听到哗哗的声音,看到了慢慢摇走的渡船。这一次,大河冰封,我们直接从河面上走到对岸,还看到两三辆车从河上开过。一辆黑色轿车从我们旁边的冰面上经过的时候,我感到略微像地震时的头晕,听到冰层细碎的声音。似乎有一道冰缝就是那个时候形成的。

庄稼都收了,桔杆还立在田边。太阳沉向西边,把大山的影子投到河的对岸。举起相机,能看到暖色的夕阳晒着黄得发红的桔杆堆,暗蓝色的河水夹着白色的冰面和砂石滩或缓或急地流过。东面的远山发亮,近处的影物在西山的影子里开始越来越暗。

除了远足、吃、泡澡,还有看电影。我带了去年买的视频线,忘了带又新买了一根音频线,把笔记本接在电视上。把旅店的三个椅子搭成个半躺椅,腰后面垫个枕头,旁边放着各种吃的喝的,看着无聊的片子我睡着了好几次。如果是平时,会因为片子耽误了时间而愤怒,恶狠狠地把它删除吧。时间,至少在这一段时光里,不再是个问题。周围都是些上了年纪的人,他们全都慢慢地行动。他们每天在吃饭的时候出现在餐厅,慢慢地领了餐盘和半素的菜,然后在座位里慢慢地吃,偶而也看看我们相形之下风风火火地吃风风火火地喝。他们吃得很慢,但是会在我们吃饭期间来去几波食客,因为我们吃海鲜很多时间很长。然后,这些老人就静悄悄地消失了,估计在每个房间热气腾腾的温泉里都躺着一位,在我们大嚼的时候,他们正细细地梳理自己的一生。

冬天的温泉,原来同学们以为是在露天,水里热气腾腾的,而外面一片冰天雪地。结果全不是这样。没有冰天雪地,只是干冷,温泉也是在每个房间里而不是在室外。不过,这于我全无分别。我所享受的,只是空闲,少些焦虑,能够靠在躲椅上看部无聊的片子,中途不知不觉睡过去。

--------------------

博客会手工同步到以下地址:



20131214

内战记

内战记

内战记,白色封皮的小册子,放在我的桌上将近一学期了,仍然没有读完。一方面,各种工作和生存压力接踵而至,难得有空从深水里浮上来透口气,另一方面,这本书引人伤心,难以卒读。

在内战记以前,凯撒写下的是高卢战记。在那本书里,他带着与自己彼此信任的军队,在山岭大河间行军,征服和说服后来法国地区的各个部落,让鹰帜插遍高卢。他的军队把原产亚平宁半岛的葡萄带到高卢,发现在这里长势更好,就像后来的征服者们播种到别的大陆的咖啡和甘蔗。

我不记得凯撒在高卢战记中如何炫耀自己的军功。这完全不必怀疑,因为从后来内战中高卢人的归附来看,凯撒应该是斩服了人心。杀敌众多,也许只是不必提及,而非回避。因为,那些被杀的都只是"别人",是你们谁也不认识的、异族的、讲着别的语言的、眼睛和头发颜色都不一样的什么。死在刀下的,是高大勇敢但是无知无识的北欧人,是日尔曼人从遥远的北方贩卖来的斯拉夫人,是北非还是哪个贫瘠沙漠里跑来的骑着马跨着枪的黑皮肤人种。他们与我们如此不同,所以,死在刀下也只是战功,没有人会皱一下眉头。

方阵压境,红袍列张。如果臣服,就是罗马之友,如果抵抗,就屠城或者卖为奴隶。多么简单。可以用强力,可以用智谋,可以欺骗,可以杀伐。狩猎之中,又有什么手段是不可以的。

终于,在高卢战记的最后,凯撒提及了他的国家。按凯撒的说法,那个收到了他无数战利品和新土地的国度和人民,背叛了他。所以,内战记的开头,我盼着凯撒杀回罗马,去与那些对他不起的元老们当面对质。

但是凯撒回罗马的路非常区折,而且罗马也并非终点。他违背法律带军回需杀到罗马的时候,元老们已经逃跑,跟着他们认定的领袖庞培,凯撒曾经多年合作的另一位巨头。

如果是年少的时候读内战记,我可能会唾骂元老们全都蠢得瞎了眼睛,不辨是非,背信弃义,庞培是坏人的总头目,理当千刀万剐。但是,现在我站在凯撒的背后,越过他的头盔看着面前的这些同样军服同样刀兵的罗马人,不禁怀疑,难道这么多罗马人都同时瞎了眼?

但是,如果这些罗马人是正义的,凯撒的一味退让,先礼后兵,委屈求全,都是做作么?

所有这些,凯撒都没有说。或者,我跳过了他的评论没有相信。我只看到罗马人杀死罗马人。罗马人在西班牙,在马赛,在迦太基,在亚历山大,在所有他们先前和后来征服过的地方集结起来,竖起战船和敌楼,努力把同样肤色眼睛和语言的另一些罗马人杀光。罗马人召唤高卢人日尔曼尔人努比底亚人来助阵,攻击罗马人。这些,全是为了荣誉正义和罗马的团结统一,你死我活的列阵双方的目的是完全相同的。



十多年前,大哥曾经劝过我事业应该如何发展。我当时说,我一点也不想做个元帅,也许你适合,但我不行。不仅没有这样的理想,而且深感厌恶。我只想做一个狙击手。或者像在CS里一样,一开战就把31或者41收起来,操着刀就冲上去,看满天手榴弹从头顶上飞过,在身后炸响。然后看到一群敌人,把主枪端起来,射击,然后死去。这是能让我快乐的部分。

研究盟军,防范与攻击友军,评估战友的动机,像对异族一样猜想他们,让我失去对整场战争的兴趣。同时,正如我总结的我的悲哀之处,是我期待别人也是这样。并且,我为大家不是这样而感到愤怒和悲伤。我总误以为如何待人,人们也会这样对待我。虽然如罗素所说,别人永远不会如你自己一样爱你,但是,我一直不能理解...我不能理解这个世界中的绝大部分人间的规则。

似乎全然没有规则。


读内战记的时候,我一直盼望突然哪里冲出个小兵,一刀把凯撒剁了。然后战争结束,全书完。这一直没有发生,直到他打败了战场上所有的敌人。在和平以后,凯撒有一次正批阅文件,旁边有人聊天谈到人应该如何死去。凯撒插进来说,应该突然死去。他后来如愿以偿,被一群罗马人谋杀了。杀人者中,有与他一起出入战场如同父子的战友。传说,凯撒勇武过人,被这群人攻击的时候一直有效反抗,却突然抛下剑大喊,"原来你也参与其中",引颈受戮。

后来的历史,超出了内战记,虽然仍是内战。凯撒的继子兼甥孙替他报仇,把这些谋杀者都打垮处决了,连同凯撒的朋友也一起打倒了。这位继子就是屋大维,奥古斯都。

屋大维这一段应该已经不在内战记中了。而且,如果我是内战记中的一个小兵,估计也活不到内战记结束。我会在某个清晨,穿戴好我的战甲,擦亮盾牌,然后随着方阵冲击,每个踏步都整齐有力,充满金属声。死命拼杀,然后在我感到疲劳的时候,在我遇到第一个看起来与我一样的罗马人的时候,我准备抛开盾牌和刀剑,看着他的眼睛,看着晨曦从对面猬集的矛尖上反射的光芒,展开胸膛去拥抱他。这个罗马人,我的兄弟。

从那一刻以后,我永远也不会知道他推开我,跨过我。当然,他也绝无机会欺骗我,背叛我,声称我是盟友然后展开攻击。甚至,在他所写的回忆录里,也不会光荣地把我列入他的故事,阻止或帮助过他的成长。因为我并非过往,而是过客。

--------------------

博客会手工同步到以下地址:



20131130

bug的定位比修改重要1000倍

bug的定位比修改重要1000倍

砍倒一棵树,当然也有技巧,一斧子一斧子劈得要在正确的位置上,还要喊好顺山倒,别砸到别人和自己。不过,在一片林子里找到你想要的那一棵,才是更重要的事,也更困难。修改一个bug,可能只是一个字符,而在几千几万行代码里找到应该修改的地方,就困难得多。就像,修改一个变量,不过赋值语句一行而已,但是查找之难使得衍生出了像二分查找这样的许多算法。

前两天跟关同学郑同学一起修改了一个bug,也体现了这一点。

1. 现象

先是关同学在电话里说:包老师一眼就能看出来有毛病,出数据的时候一顿一顿的。

这个问题描述的是一个上位机/下位机结构的设备,所谓一顿一顿的,指的是上位机从下位机读了数据以后,显示的时候不流畅,卡。

我们先回顾了一下,先否定了这不是存在已久的同一个问题。以前提到过一次是怀疑优化显示效果的滤波器效果不好,后来证实是选择了错误的滤波器导致的,滤波算法本身无误。后来这个问题从我的视野里消失了一段时间。

这几天对上位机代码重新组织,有大的设计变动,所以关同学怀疑是变动中引入了bug。这是一个典型的正确思考方法:哪里有变动或不同,哪里就需要怀疑。但是实验表明,变动以前的版本也存在同样的现象。 (有效的版本控制多么重要)

以上,我还没有亲眼见到,都是电话里得到的消息。然后我亲眼见到,确实一顿一顿的。这也是解决bug的重要原则,即不能相信任何人的描述,一定要亲眼所见,一定要重现故障。如果病人对医生说"我发烧了",医生一定掏出个体温计测一下,而断然不会直接开退烧药的。

亲眼所见的,也还不够。我祭出神器bushound跟踪了usb的数据。绝大部分数据包都是30ms左右一次,每次实验里总有那么两次是600ms左右。将近半秒的时间,怪不得人类已经能够感觉到了。我们保留bushound的日志,作为证据--好了,bug确实存在。

这是第一步。

2. 猜想-设计实验-再猜想

一旦认定bug存在,接下来是定位bug,最后才轮到修改。

2.1 由整体到部分

定位bug,首先要对整个系统有个认识--模型或者说是猜想。我们以为整个系统是这样运行的,分成几个部分,它们之间如何藕合,数据沿什么样的路径在其间流动,有哪些控制信号。

当我们把整体肢解为部分以后,接下来,我们既可以假设bug可能在某个部分中,也可以一个一个遍历所有的部分,依次查找bug是否在其中。前者,据说是中国/东方的经典手段,后者是西方/德国的特色。传说,同样是一根针掉到地上,中国老太太会根据针掉落的方向、自己的坐姿、声音传来的方向,根据这些启发信息去查到特定位置,而德国老太太会把地板划分为M*N个小格,然后把每个小格找一遍。这两种方法也无所谓优劣,在实践中,有时一下瞎猜对了,确实效率很高,有时则找遍所有的模块,最后才命中。不论如何,事先计划查找的路径,查找中记录走过的模块,是很重要的。

2.2 孤立

在查找某个部分时,我们要把这个部分从整体中孤立出来。调试方法中的"最小系统法""对比法"都有此意。在最小系统法中,我们把其余的部分都去除,单单观察认为可能有问题的部分,确认它正常或不正常,从而避免其他部分的干扰。在对比法中,我们令两个系统其余的部分都是相同的,只有要观察的部分不同,这样,现象的不同就只能来源于要观察的部分的差异--而差异就是导致不同的原因。

对比,既可以是两个系统同时运行,也可以是同一个系统,修改配置以后两次运行。最重要的,保留运行的记录作为对比的证据。

我们首先猜想,是不是下位机有毛病啊,它可能向上传数据时,某些时候比别一些时候慢半拍。下位机是包师弟写的,他说:证据哩。

我见过很多工程师在这种时候开始争论,A说是你的毛病,B说不会,A说一定是,B说一定不是。我在这时对别人说过,这么争论毫无意义,咱们各自拿出证据来吧。他又说一定如何,我就只好走开。

证据,需要隔离其他的部分,把你假设有毛病的部分孤立出来。

我们应该开始剥皮,期待把下位机单独地暴露出来。但事实上,我们当实对下位机的怀疑到此为止,因为包师弟有个神器,他用labview做了第三方的工具,能模仿上位机的行为。所以,他只须出示labview的程序运行的结果无误,就证明了我们的上位机有毛病。这就是对比的力量。

我们转去孤立别的部分。我们只保留上位机读数据的那部分,把解析数据、显示数据全注释了。上位机代码空转,用bushound跟踪读数据的速度。结果表明,即使上位机除了读什么别的也不做,也有间隔600ms的时候。这表明,bug (或者说延迟/lag)不是由解析数据和显示数据引入的。

这时,问题仍可能是由下位机引入的。但是,找bug也可以有条不紊一步一步来,而不是东一榔头西一扫帚想起来哪就查一查。我们继续切掉可能影响性能的上位机代码,直到,就剩了相当于ReadFile的一句。

题外话。我们以前被ReadFile手册骗过,或者说我们没有成熟到那种程度,认识到ReadFile这个API的后面是由驱动支持的。换句话说,驱动如果有毛病,ReadFile的表现就跟手册里写的不一样。我们以前遇到的问题是,实验表明根本没有读到数据,但ReadFile的返回值和参数里的out部分按MSDN里的解释,数据应该读回来了。后来才想到,ReadFile依赖的是我们自己写的驱动,而这个家伙不像我们想的那么靠谱。大多数情况下,它的表现都不错,但是沧海横流的时候,它就被吐跑了,而这正是我们需要它的时候。所以,这个故事告诉我们,实践是检验真理的唯一标准。你以为自己多么正确也是白扯,不能解释客观事实的猜想都只是猜想而已。题外话结束。

这次的ReadFile是阻塞读 (同步读) ,它后面的支持就是郑同学写的winusb驱动的DLL部分了。我们准备在这一句以前和以后各加一个console输出,把ReadFile花费的时候输出来。关同学质疑了一下,console输出影响性能,解除疑虑也容易--试一下呗,万一对性能的影响可以接受呢?瓶颈也许不在这里。

console输出表明,ReadFile性能不稳定,大部分时侯耗时30ms左右,个别时候600ms。个别时候就是我们关注的地方。同时,console输出启用了debug模式,暴露出郑同学在winusb驱动中输出的一些console调试信息在发布的时候没有关闭。这显然会影响性能。

2.3 修改bug和测试

郑同学去除winusb驱动中的console输出,发布了新的驱动。关同学把更新驱动以后,跑了一遍,console和bushound都表明,没有600ms这样的延迟了。

这一步耗时很短。就是按你的猜想改bug,改完了以后测一下,与此前的记录对比。如果毛病没了,那么你十有八九猜对地方了。

2.4 恢复现场和测试

切掉或修复有毛病的地方以后,还要把为孤立这个部分做的工作恢复成原样。我们把解析数据和显示数据都恢复了,去除console显示,再测,bushound表明性能符合要求,bug的现象消失了。

3. 总结

bug的最终解决,上位机代码没有作任何修改,虽然整个bug定位都是围绕上位机代码展开的;只有winusb驱动的代码作了修改,虽然整个定位bug的过程中都没有深入过其中。

这个故事告诉我们,事情发生的背后,往往深藏着很久以前或更加深刻的原因,这些原因在世界运行的法则驱动下,在很久以后或层层推演,才以表象的形式出现在我们的面前。科学和工程的责任,正是猜想然后发现地下暗河存在的证据,通过修改在直觉认识上似乎毫不相关的变量,从而改变这个世界运行的结果。除了人本身不可以被操控,可假此以动太阳而移群星。

--------------------

博客会手工同步到以下地址:



20131116

挺简单的?那你就自己整

挺简单的?那你就自己整

人生在世,会遇到各种奇葩。奇葩们观我,估计也应如是。

二猫妈和大哥分别告试过我,别在工作中掺杂感情因素。但是我只喜欢做乐于付出感情的工作,没感情的,连碰也不想碰,不然有的科目怎么会不及格好几次。所以,只好按马老师教导的"要注意态度",然后自己生闷气。

有些人,仿佛他们生出来就注定了没法跟我合作。衷心希望他们这一生能离我远点,这样他们自己也能过得幸福快乐一些。

一类人是"挺简单的"人。他们在交给你一个任务的时候,乐意像幼儿园老师一样补一句诸如,"这活儿挺简单的,容易干",又或者"你们这么厉害,一下子就能搞定"。我没上过幼儿园,对于幼儿园老师的刻板印像完全是瞎猜的。也许,幼儿园老师也不会把讲话的对方看得如此弱智。

这活复不复杂,简不简单,费不费劲,我自有我的评判,你真是犯不着在这多余罗唆,附加贬低我的工作,我又不会因为你这非专业的评论就降低对报酬的要求。

我第一次回应这句话是对小丁老师,也是因为关系实在不错。此前,我都是忍了,然后在做费用的时候报复性提高价格。小丁老师当时说,"据他 (还是她)说,这个挺简单的。"我说,"一定不简单。要是真简单,他就自己整了。"

后来,我做了些类似的回答模板,视心情回复给对方。比如,要是简单,你就自己整吧;要是简单,你就找别人吧,我忙着呢。

这帮家伙有点像我十多年前遇到的老板们。那些老板习惯性用语是,"你顺便把什么什么做了吧","这个活还挺锻炼你能力的","在这个活里,你还能学到啥啥技能"。用人,还要表示我所用不多。杀人,还要表示我的刀够利,因此你死得不那么痛苦。给你干活,或者等价交换,我还要感谢你怎么的?

也可能有的同学会说,人家的意思是夸你呐。这活儿他本人干着可能不行,但是你干就特顺手。这样想的老板们,适合到马路边找个力工大哥,说,"看你一身腱子肉,闲着也是闲着,帮我把半吨扛楼上去吧。"然后别走,看腱子肉揍他不。

而且,这么说的老板,据我所知,通常真的认为你干的活非常不值钱,任谁都能轻松完成。活,可以干,但是我们绝不能自轻自贱,也绝不允许别人轻贱我们。而且要收钱。

说到收钱,还有另一类人,如果他们不出现在我面前,他们和我也都会感到更幸福。这一类人和"挺简单的"那一类人比,有更深厚的文化传统。他们一般先不谈价格,而是说"必有重谢",或者"忘不了你"。十几年前有些老板对我说过类似的话,"小杨,我一定忘不了你"的老板们,现在估计他们都不记得我了。但是我记得他们,我还记得等到付钱的时候,他们会说"哎呀,实在是紧张/投资失败了/这把项目做亏了"。后来遇到这样的人多了,我就有了答复模板,"你失不失败跟我有个P关系,成功了你又不多给我钱",或者"如果你现在真没钱,那咱们谈谈股份吧"。

这帮家伙遵循着丛林法则生存,或者说,他们自以为有咬人一口占了便宜跑得了的本事,甚至有些还认为自己不仅这次能跑,以后有机会还能跑回来再咬一口,而且还有能力再跑。董同学指着在马路上违规加塞的前车,把这种行为称为投机,并认为投机得利会破坏规则。董同学所定性被破坏的规则,显然不是丛林法则。

他们打算步步为营,能多赚你一分就多赚一分,在讨价还价中不断试探你的底线。他们在寻物启示和招聘中写着,"必有重谢""报酬面议"。郑同学说得好,"什么是重谢"。我下了决心,如果捡到巨宝,看到寻物启示里写"重谢"而不标明数额和支付期限的话,我就把巨宝扔伊通河里去--自己留着是不当得利,但是我没有替他保管的义务,就当不知道那是什么玩意。

我曾经在饭店里旁听两位食客的对话,1999年,跟二猫妈在地质宫旁边吃羊汤肉夹馍的时候。一位是老板,一位是潜在雇员,他们是朋友。潜在雇员一直在问报酬几何,老板一直在说"我还能亏了你么""那还能少了你的么""咱还能赔么""咱俩之间,还得有个准数么"。是的,需要。要么,有准数,你挣得更多我也不多要,你全赔光也跟我没关系;要么,按比例,我们利益同进退;要么,我就是赔你玩,啥也不要,但是,那得我乐意。这些都要事前明确约定,可以变更,但要我同意才行。很多年以后,我逐渐下定决心,欠我工资的,我一定把他挂在塔吊上,而不是把我自己挂上去;我说你是我兄弟的,你才是,钱一分也不必谈,谈了伤感情,我说不是的,你就一分钱也不能少。



又,发上述感慨,当然是因为不爽到了一定程度。起因是七转八转的关系,一个家伙找到我,然后变成了我求某同学做个私活。此处略去经过。整件事情整得我非常愤慨。天下多奇葩,以后再有这样的事,麻烦各位同学一定要提醒我,就说"你想想以前那次,还有那次。"尤其是关同学和郑同学,请一定提醒我。我先提前多谢提醒,届时会为你的提醒支付巧乐滋。


又及。以前有同学提到过,看你写的说的,好像快意恩仇的样子,到头来还不是要向世俗低头么。是这样,你的理解和我的表达有所不同。我快意的是我的恩仇,不是你的恩仇。所以,一切都是由着我的性子来的,不是演给你看的。并且,不是因为我有本事这样,而是,我能够承担这种快意的结果,也愿意付出快意的代价。而且,我也的确付出了。打人的时候就得想到自己手疼,及事后要支付的医院费,及自己可能被反击。都非常惨痛,如非经过同样的专业训练,请勿模仿。

--------------------

博客会手工同步到以下地址:



20131030

在Lenovo的Y430上安装Ubuntu,用nomodeset解决soft lockup

又临时整了一台笔记本,是联想(Lenovo)Y430Ideapad系列,预装了Vista。我拿到手的时候里面不知道装了windows的哪个版本,迟疑了一下要不要安装双系统,然后决定装Linux覆盖原来的windows

 

还犹豫了一下要不要装Debian或者ArchDebian,我没有马上找到Windows下的U盘制作工作,所以放弃了。还是Ubuntu。我希望上手就能工作。

 

U盘安装,启动以后在文本界面上报类似"BUG: soft lockup - CPU#0 stuck for 23s!"这样的错误,然后试图杀死一个进程,名字忘了,大致是检测CPU的。

 

搜了一下,[http://ubuntuforums.org/showthread.php?t=1466577]是最有效的。核心是nomodeset。我安装的是Ubuntu 12.04 LTS,具体操作略有不同。

 

在启动时按F6,选英语;然后在菜单里选择 option;在右下角的菜单里用回车选择nomodesetESC退回;选择"Installing Ubuntu还是玩意"。安装完成以后,安装系统会要求重新启动,把U盘拔了,重启。

20131026

图书防盗磁条原理

图书防盗磁条原理

书店、图书馆、超市、商场的出口两侧有两根立柱或立面,持未"消磁"的商品通过,就有喇叭滴滴叫,然后彪型大汉冲出抓住你的两臂带走。这种被统称为"磁条"的检测技术事实上不止一种,而且也不都跟磁有直接关系,网上的传闻多种多样,且夹杂诸多谬误,所以整理一下我查到的靠谱的。

下述磁条防盗技术的讨论中,不包含彪型大汉部分。也不包含如何 防防盗磁条。

先讲两个简短的,包括射频和声磁,然后再详细讲相对更通用的电磁波防盗。

1. 射频

射频磁条长得样子像个硬纸卡,夹在书的最后一页,或者贴在商品上,2-3cm见方。把硬纸卡掀开,里面有一根导线,盘成转角方正的蛇形。有一些蛇形的中心处还有一个芯片。

防盗门一直在发射电磁波。当射频磁条通过防盗门的时候,防盗门的电磁波激励磁条上的导线。

这根导线相当于收音机的电线,这时接收到能量。如果有芯片,这些能量会支持芯片工作,然后受芯片指挥发射电磁波。这一电磁波被防盗门上的天线接收。这样,完成一次通信过程。

有芯片的这种,跟RFID,或者称射频卡的工作原理是相同的。卡里的芯片可以发射指定的编码,还可以与防盗门"对话",确认芯片的身份,因此可以识别事先编号的这个芯片所对应的人或者商品。

没芯片的我不太确定。大致应该是磁条上的天线在防盗门电磁波的作用下"受迫震动",发射电磁波。磁条发射的电磁波频率磁条的导线 (天线)特性有关。如果防盗门检测到磁条发射的电磁波,就叫。

关于破坏性检测。一,防盗门检测电磁波的时候,应该是有一定的频率范围,所以如果不彻底破坏磁条天线,而只是剪短一段,估计还是可以检出。二,RFID里面有CPU,能够通过往复通信回答防盗门或检测器一段你很难仿造的消息,而且这段消息每次可能不同。所以通过监听重放这样的技术,估计不能冒充别的RFID卡。

2. 声磁 (AM)

射频这个,只能防君子不能防小人,因为非常容易找到揭掉。所以声磁 (AM) 和电磁波 (EM)防盗应用更普遍。其中声磁技术似乎更贵一些,所以主要用在高价商品上。声磁的好处是检测距离比电磁声防盗更远一些,磁标签工作在1.2米~1.5米,电磁波磁条工作在0.7米~0.9米。

声磁的原理。有一种材料,叫做玻莫合金。它的名字来自英译 permalloy, 其中perm- 表示导磁性 permeability,-alloy是合金。玻莫合金的成分是铁和镍,所以也称铁镍合金。玻莫合金除了导磁能力非常强以外,还有有诸多特性。

人类每当发现什么东西有特别的特性的时候,就会琢磨把它应用在某个领域上。

玻莫合金有个特性,叫做磁致伸缩。在不同强度的磁场作用下,玻莫合金的长度会发生变化。这种微小的尺寸变化可能是人眼难以觉察的,不过对于微小变化的检测,更高精度的检测,这可能也正是现代科学发展的一个重要特征。如果磁场是交变的,即它的大小和方向 (正负或南北极) 不停发生变化,那么磁致伸缩的尺寸也会不断变化。快速的大小变化,就是振动。如果外加的交变磁场的频率与这块玻莫合金的固有频率一致,受迫振动就导致共振。和谐、和声、共振,都有点这个意思,同声相求。

接下来就简单了。防盗门发出交变磁场,这个交变磁场的频率与置于图书中的玻莫合金的固有频率一致。如果玻莫合金存在,它就会按这个频率振动--发声。防盗门检测这个声音,如果声音存在,就有图书被带出。这个声音的频率在超声波频段,人耳听不到。

以上两种,要么关掉防盗门,要么把RF卡或声磁拆下来,都会叫,没有下面提到的电磁波防盗磁条的充磁和消磁功能。所以,RF和声磁材料,都是一次性的。

3. 电磁波防盗磁条 (EM)

电磁波防盗磁条也是玻莫合金,不过,原理不同于声磁的磁致伸缩。但是,也不是朴素的猜想检测磁场。

3.1 永久磁条

永久磁条是不能"消磁"的磁条。事实上,它不是磁条,而是玻莫合金。陡峭磁滞回线的坡莫合金。

现在我们解释一下陡峭磁滞回线的坡莫合金有什么特性--因此可以被检测出来。

什么是磁滞回线呢?下面涉及到磁感应强度B、磁场强度H等术语,不懂的话无所谓,你就假设它们都是磁场的大小就行了。当外界的磁场强度H发生变化时,玻莫合金里的磁通量密度也跟着发生变化。这条因变量B和自变量H间的函数曲线,就是磁滞回线。

什么是陡峭的磁滞回线呢?我的瞎理解,就是B和H的比值,即斜率,总体来讲比较大。或者说,当外界H发生微小变化的时候,B就发生较大的变化。这让我想起精神病、文艺青年、易激惹等诸多人等。陡峭的一个结果是如上所述,对外界的变化反应比较明显 (因而容易检测),另一个特点是,它也容易饱和,即稍微加强一点的H,曲线就脱离了原点附近,因而B不能再随H的变化而变化了。这个特性后面稍晚会用到。

先说陡峭。我们还注意到,这条陡峭的磁滞回线还有个特点,它不是线性的。当H变化的时候,产生的磁场不是线性变化的,而是弯弯曲曲--非线性的。这会带来什么结果呢?第一步,变化的H导致变化的B,这是磁滞回线;第二步,变化的B导致电场,这是法拉第电磁感应定律;第三步,因为B的变化是非线性的,因此电场不是稳定的,而是变化的;第四步,变化的电场导致磁场,这是楞次定律;第五步,变化的磁场与变化的电场,产生了电磁波。所以,非线性磁滞回线的结果,是产生了向外辐射的电磁波。

当玻莫合金通过防盗门时,防盗门产生交变的磁场,交变磁场作用于玻莫合金,玻莫合金向外辐射特定频率 (频率取决于玻莫合金作为天线的特性,电磁场这科我不及格,完全不懂)。防盗门如果检测到这个特定频率的电磁波,那就是有图书外带。

3.2 复合磁条

永久磁条很薄,一页纸那么厚,窄,韭菜叶那么宽,夹在书里很难找到。所以才能用来防盗,免得被你找到揭走。但是它有个缺点,就是不能消磁。所以从书店买了书,带到图书馆出门的时候会叫。这直接导致我十年前跟门卫和领导间的争执,然后换了个单位。

所以后来有个东西,也是这个尺寸,却是可以消磁的,叫做复合磁条。

复合磁条,是在玻莫合金上再贴上几片半硬性材料。什么是半硬性材料呢,这要从软性和硬性磁材料说起。

软性材料,就是外加磁场,它就产生磁性,撤掉磁场,它的磁性就迅速消失。玻莫合金也属于这一类。变压器里的铁芯,又叫软铁 (物理硬度不差,刀割不动) ,也是这个东西。与此相对的是硬性材料,外加磁场以后,不容易产生磁场,但是一旦产生,就始终不渝,很难消除。半硬性呢,介于二者之间,不那么容易加上,也不轻易消失。

复合磁条"加磁"(或者确切地说叫 充敏)的过程,是把复合磁条放在变化的强磁场中,这一变化强磁场导致复杂磁条中的半硬性材料失去磁性。请注意,是失去磁性,而不是得到磁性。所以,"加磁"这个词很有意思,它的真正作用是"去磁"。

加磁/充敏 以后的复合磁条,当它置于防盗门的交变磁场中,由于半硬性材料没有磁性,所以跟永久磁条的环境没有区别,玻莫合金发出电磁波。"滋滋~~"防盗门就叫了。

复合磁条的"消磁"(或者确切地说叫 消敏)的过程,是把复合磁条放在强磁场中,这一变化强磁场导致复杂磁条中的半硬性材料得到磁性。消磁,是半硬性材料获得磁性的过程。

消磁/消敏 以后的复合磁条,玻莫合金受到紧贴着的半硬性材料的磁场强度的影响,已经饱和 (请回忆我上面提到,下面将会用到"饱和"),磁滞回线脱离零点附近。此时防盗门外加的交变磁场作用于玻莫合金,就无法得到陡峭的磁滞回线,所以,也就没有变化的磁场、变化的电场,因而也就没有固定频率的电磁波这回事了。

想使用永磁铁消磁/消敏或代替半硬性材料的同学,你基本可以死了这条心。需要的磁场强度非常大,能感觉到隔着20厘米左右的手里的铁件还被吸叫。咱们一般见的磁铁,能感觉吸引,也就三五厘米吧。不过你要是试验成功了,也别和我们分享。

再充磁,就是再让半硬性材料去除磁性,因此玻莫合金有效;再消磁,就是让半硬性材料带上磁性,使玻莫合金饱和,因而失效。

所以,充磁失败,或者过了一阵,"磁性"又回来了,就是半硬性材料失去了磁性,所以玻莫合金又有效了。表现为得到的,其实是失去;表现为失去的,才真正得到。


--------------------

博客会手工同步到以下地址:



20131021

世界运行的原理: 东北师大物理系门口的公式

 

前文书提到,暑假的时候大学同学聚会,我在物理系门口惊见师兄们也在聚会。我们认养了一棵松树,他们则在门口立了块纪念碑。

 

如图所示,该纪念碑上雕刻了4个公式。当时看了一眼,只认识两个,顿时虽然不明白但是觉得很厉害的样子。这两个仅认识的公式又来头很大,想来剩下的两个定是更牛。今天终于找出点时间查了一下要知道,从名字查公式容易,从公式查名字就完全不同了,公式的输入就是个难题。就像建模,从模型推导结果相对容易,给你一堆数据,让你猜是什么模型就困难得多。

 

闲言少叙,公式的意义如下。

 

第一个公式是 薛定谔方程,描述波函数的量子行为的,发表于1926年,
参见[http://en.wikipedia.org/wiki/Schrodinger_equation]

 

第二个公式是 爱因斯坦场方程,描述广义相对论的,发表于1916年,
参见[http://en.wikipedia.org/wiki/Einstein_field_equations]

 

第三个公式是 质能方程,用来指导造原子弹的,发表于1905年,
参见[http://en.wikipedia.org/wiki/Mass%E2%80%93energy_equivalence]

 

第四个公式是 牛顿第二定律(及加速度与位移和时间的关系),描述牛顿时空观,发表于1687年,
参见[http://en.wikipedia.org/wiki/Newton's_laws_of_motion]

 

4个公式似乎是按时间倒排的。


我们对于世界的认识是精确的和定量的,精确到连哪里仍然是模糊的和模糊到什么程度也清楚。薛定谔的猫啊,爱因斯坦拉小提琴和灵感啊,牛顿的苹果啊,这些不过是用来骗骗看热闹的外行,增加科学家人气的。


真正的精确,与模糊之间,所差何止千里。正如糊涂与清楚之间的距离,跨跃之后,就是动物与人类的差别。

 

顺便说一句,这雕塑是高哥设计的。我当时看了就觉得是他,短信一问,果然。


20131010

铁子同学的太原科幻之旅,我检查嗓子的科幻之旅

今天中午见了铁子同学,一起吃了狗肉。为避免不吃狗肉的同学误解铁子同学,确切地说,我吃了狗肉,铁子同学有没有吃,我倒是没有注意。

吃完离开的时候,乌云密布,整个天跟日食一样黑。我们跑出饭店,发现外面狂风大作,湿气扑面。我们大喊,"跑啊!"冲进了正在开始密集的雨点里。我们的前前后后,很多人影穿棱而过,或者被我们甩在身后。刚钻进车里,风卷着雨水和尘土扑天盖地而来。

一边扑落脑袋上的雨点,我一边后怕。因为腰间盘,每当有雨都得分外小心了。在石家庄出差,顶着小雨打车,不到半分钟,我就很担心,结果果然等到电梯的时候,左腿已经瘸了。腰间盘突出会压迫脊椎的神经,这是突出以前很多人从来没想到的,腿疼居然会是腰的毛病。想想不少恐怖科幻类的动画片里,巨人吃人的时候,不是要把人的大脑和脊椎神经一起抽出来么。脊椎神经是人类的神经系统中重要的一环,密集程度仅次于脑。

而这一次也淋到了雨,还狂奔二百米,居然没有疼。可能,是因为铁子同学的年轻和热情传染了我。吃饭的时候,主要是他讲,我听,讲他前几天在太原参加科幻大会的情形。"我见到大刘啦,还有何夕也一起合影,我左拥右抱...小姬,小姬挺高的,夏笳也挺高;还有那谁也是,真高啊,快跟我一样高了...我坐了22个小时的车才到太原,还是硬座..."

他带回了一本特别厚特别硬皮的科幻牛人们的传记,要送给我。我说,你还是自己留着吧,给我真就白瞎了。迷上科幻以后很多年以后,我才知道,我根本不能算是科幻迷。像大刘老师那种大学期间就把各种八卦掌故 (及历史) 都了解得极其清楚,像铁子同学这种单程20多个小时往返硬座自费参会,才是真正的科幻迷。铁子同学说,那本大硬书,他要留给东北师大科幻协会。他说,他毕业的时候,要把一箱子书都捐给协会。我说,是啊,应该让更多的人能够看到。但是我却舍不得把一本书借给别人。在这里,我该呵呵还是嘿嘿一笑?

铁子同学感叹,这协会现在不好整啊。我脱口而出,现在大家都只想要成绩不想干活。他说,啥?愣愣得看着我。我的确没有想到,在他已经大四,同时协会已经不在团委列表中的情况下,他仍然把协会的发展视为己任。估计,我这句随便的抱怨刺激到了铁子同学。抱歉。其实,这并无针对性,并非你或者科幻协会给我这样的感觉,而是--与其说是因你这句而感慨,不如说--整个世界尽皆如此,令我失望。

我给铁子讲了什么?我说,我前两天去嗓子出毛病了,去医院,看到那个检查设备很科幻。具体是这样的。

我嗓子前一段总感觉像出血,咸,还有红色的东西。某一日,被不知道什么纤维扎进了扁桃体。什么,你问我吃哪种鱼了?天可怜见,上回吃鱼是和包师弟,吃的 酥鲫鱼,被扎了,在右边扁桃体,约半厘米多长。再上次吃鱼,似乎已经是好几年前的事了。这次扎我的纤维,应该是某种蔬菜,白菜之类的。

医生揪住我的舌头拉出来,她额头上的灯的在我面前左晃右晃,说,"那不是血,是淋巴渗出液。"后来我跟大哥转述的时候,他说,"什么玩意,淋巴还能渗出呐?"我心里默默地替他翻译,"不明觉厉。"

医生继续揪住我的舌头,命令我,"说'衣~~'"。我试图把舌头抽回来,失败了,只好说,"哎~~",心想,"不是一般都说'啊'吗?"

然后医生让我做电镜。怪吓人的名字,再次不明觉厉。不过拿到麻药小瓶的时候我就全明白了,某种麻醉剂,名字现在我一时没想起来,不过看到就知道是啥了。喝了,等。我以前拔刺的时候喝过,这是避免咽部本能的呕吐反应的。

躺在几块平板组成的椅子 (床?)上,一看那几块就是对应人大卸八块以后形状拼起来的。后脑勺正夹在一个坑里,45度侧对医生。我看着他,他的脑袋后面是个电视,手里拿个跟章鱼脚一样灵活而有韧性的黑线,线的末点晶光瓦亮。我盯着这黑章鱼弹跳,不由得想起《黑客帝国》里的Neo,那帮人按着它,把个很多条腿的家伙塞向他。

但是我表现得很勇敢,面对章鱼大张开口,无声大叫,"来吧。"医生看看我,温柔地说,"闭嘴。"

我闭上嘴,想,"看来是一会儿才检查,歇会。"正在此时,说时迟到时快,我的鼻子眼一痒,眼看他脑袋后的电视里出现了一团烂肉和毛发...还没等我反应过来,医生说,"不要怕,没事。"

原来,电镜是从鼻子进去的。我原以为和拔刺一样,从嘴进去。问题是,我没有怕啊,却完全无从解释,就跟拔牙的时候一样,只好瞪眼。

医生说,"不要怕,你别看屏幕能感觉好点,",又一遍,"吸气。"

我吸气,管子从鼻子进到咽喉。

医生说,"说'衣'"。又是"衣",所以我又困惑了一次,为什么不是"啊"。

再吸气,管子回到鼻子,然后就出去了。

医生说,"慢性咽炎,别喝酒别抽烟。"我说,"我..."想起了以前说是脂肪肝的时候,问医生咋整,他也说,"别喝酒。"我也被整得哑口无言。因为我根、本、就、不抽烟不喝酒。

想起了ZHUMAO的一段言论。他抽烟,同事说,"大夫不是说你嗓子坏了不让你抽么?"ZHUMAO说,"我嗓子没坏的时候,大夫也说别抽。"

妙答。那我不抽不喝的,却如何应对别抽别喝这样的要求呢。我只好小声说,"我喝咖啡。"医生看了我一眼,说,"啊。"

--------------------

博客会手工同步到以下地址:



20131006

特别特别繁忙的九月

特别特别繁忙的九月

(照片有些好的,还没时间整理。)

我曾经计划过,在CSDN上每个月都发4篇以上的博客,这样能始终挂着"持之以恒"勋章。九月,我只发了两篇,前一篇是刚刚开始忙碌,后一篇是发现忙碌貌似永无绝期,月底将至,尽力而为写的。这期间,我甚至都没有时间去考虑"啥时候能忙到头"这件事。

以前有人说过,过一阵就好了,或者,这只是开头,以后就好了。我当时表达了反对,明天总是跟今天差不多,如果不是更坏的话。估计被鄙视了吧,不过事情却正是这样,王子与公主从此过上幸福的日子这种事,只有童话里才有,而且还得是在童话结束的时候。

九月之前,我保持每天读书4个小时,分门别类,各自有进度。就像大河上木筏子里的人,满怀希望地看着前方的地平线,两岸如画,柳风清扬,阳光和暖,心里盘算着,恩,还是多少公里折合多少天,我们就到入海口啦。然后就是港口和海鸥,大碗喝酒大块吃肉。可是,谁又知道下一刻暴风骤雨突然降临,河床塌陷漩涡急转,瀑布之声如雷贯耳就在左近。刚进入9月,我的整个读书进度几乎完全停滞。

你永远也想不到明天是个什么样子。当然,能看到明天,就是幸运了。就像一场游戏,你在战场上奋勇拼杀,而偶然因素像丛林里的野狼,在你左右窥伺。满以为发现了规律,比如金甲虫喷一会远程炮弹就仓库空虚,可以派小狗凑上前去群殴猛咬,你终于能放开手脚大干一场。突然,游戏的对方(可能是电脑)突然宣布,它输了;或者,敌人的空军从天而降,像一场大风,然后战场上你的兵全没了。无论是被敌人灭掉,还是敌人撒手不打了,都令人同样郁闷。

而我,只有呆坐在电脑前面。整个九月,我大部分时间就在电脑前傻坐着,屏幕上开着的主要是WORD。而大部分时间,我什么也没有键入,只是在想。或者,我站在白板前,半天也不画上一笔,我在等,等各个元素和它们的关系如水退潮,逐渐从迷雾里显现出来。什么时候,能不能,我也说不好,很没有把握。

罗素这样讨论过牛顿在太阳系各行星运行规律中的价值。他说:如果太阳系的流星们更大一些,牛顿定律就没多大价值了。因为更大的流星会经常把行星击打得四处翻滚流窜,虽然牛顿定律仍然是对的,甚至还可以根据它计算出流星与行星相撞以后各自的轨迹,但是这对于预测太阳东升月亮西落的时机难有帮助,更不用说对日食月食火星轨道的解释了。如果偶然因素在这个世界上占据了主要地位,那么,我们也不用再奢望预测未来了,连形成现在的那些规律也难以发现和了解。可是,如果未来一切注定,今天我们就能看到二十年后,那人生还值得去过么?

你可以想到,人生也许应该是那样吧,大河缓慢流淌,偶有微澜,又不至于落水。可是如果这一切都按你安排地去演,那和过家家又有什么区别。

九月,异常繁忙,没有时间写博客,也没有时间想更多。几乎,除了工作,就是倒头便睡。不过,我还是看完了CSAPP,从201211月至201310月;西方哲学史的前苏格拉底时代快要看完了。

九月过后,然后就是十月了。

20130929

去大连

去大连

早晨6:30起床,晚上21:00回到长春。这中间,我去了一次大连见老师。可能,你想到了梁朝伟提到的早晨在哪哪,中午在哪哪,晚上又回来,中间在哪哪喂了鸽子。

没那么浪漫,至少我的这一趟不怎么浪漫。

6:30起床,然后简单早饭。公交人很挤,路上车不是很挤。之后坐轻轨约1小时,到火车站,打印车票。这时8:00左右,基本没候车,检票上车了。

8:22,高铁从长春发出,奔向大连。一路无话,我拥有了难得的约三个小时的阅读时间。读CSAPP最后一章,读了两个小节;西方哲学史,读了一个小节多一点;《天行健》小说,第二部读超过一半。二等座挺挤的,对于稍微胖一点的人来说,可能就不能忍了。

11:50,大连。出站以后回头看,"大连"二字,硕大的暗红色,隐在雾气 (还是灰尘?) 里。打车去大连理工大学,预计车程1小时以内,结果30多分钟到达。司机一直很关心一个问题,"哪个门"。我也估计到了大学之大,所以事先查了地图,言之凿凿,"到了地方我就知道了"。结果司机提前拐弯了,进入我完全陌生的街道,我说,"绕吧,绕绕我们就找到了"。

最后,我提前下车,步行按指南针、GPS和地图找到了预定的目标。在百度和谷歌上都标着"化学楼"。

小广场上,有一群同学正写生。也许是写生,支着大画夹子,但是兴高采烈地样子,又似乎不像画画那么安静。我问某位同学,"请问化学楼怎么走?"

"化学楼,你问哪个化学楼?"

啊?居然有好几个化学楼。附近有几座楼,都有很长的名字,似乎其中都嵌了化学二字。那同学一指,"你找老某啊,应该就是那个。"

我蹭蹭爬上四楼。时间还早,但是我在见老师之前得先在走廊里或小广场台阶上把干粮吃了,免得见老师的时候血糖太低头脑迷糊。吃饭之前得先熟悉一下地形,免得到了约定的时间我还找不到地方。此时,距离约定时间还有1小时。

四楼,老师此前短信过我房间号。但是,当我到了四楼,傻了,老师告诉我的房间号里没有"ABCD"啊。这座楼的房间号,类似于201A,201B...。是这座楼么,我开始怀疑。

开始问路。我不知道1个小时的时间够不够我找到目标地址,饭暂时不能吃了。

我问路过的一位同学,请问这是"化学楼"么。她说,"化学楼...化学么...你问别人吧。"然后跑了。当时特别想找个镜子,难道我看起来很吓人?后来我又问了几位同学以后,开始逐渐明白了。

之后,我问了十位以上的同学。他们中,大多数不知道化学楼在哪里。没错,这十多位都是"同学",至少根据我在大学里生活了近20年的经验,他们都是本校同学。对于这一点,我也很震惊。

当终于有一位同学说,"你问化学...是苯环儿楼吧",我以为自己豁然开朗,我以为。原来应该问"苯环儿楼"啊。接下来的几位同学,对"苯环儿楼"这个名字也摇头,其中一位还问,"什么?北环楼,B环楼?"你知道,我口齿还算清楚,能快速说出几个绕口令。这位同学,似乎没有听说过苯环儿这种东西。

最后,一位同学指着就在我眼前的那座楼说,"估计你说的就是那个。"我打量下这座楼的边角,是的,差不多,看起来是个圈楼,而且极可能是六边形。

可能你已经开始质疑我的寻路能力了。不过,在以上半小时以上的过程中,我穿过小半个大连理工大学主校区,路径最短,中间还从梯子上翻过了一座墙。

见老师,谈了不到半个小时。老师拍着我的肩膀说,"行了,你回去吧。"我真想拍着他的肩膀说,"老师,你太够意思了。"但是辈份所限,这动作太没礼貌,没有实施。

半个多小时的出租车,我回到火车站。去麦当劳,坐了一个小时,吃刚刚计划但是没有进行的午饭。

15:30左右,去候车。候车室里有很多椅子,有更多的旅客。有些旅客站着,还有些坐着。坐着的旅客,很多把行李放在自己身旁的椅子上。对公共空间的侵蚀,我一向痛恨,但是现在我连问"这里有人么"也懒得问了。我想,这是年龄大了的一个标志,我已经没有热情参与这个世界的道德建设了。我想的是,就这么地吧,我自己旁边有空座的时候不去占就是了。

检票以后我注意到周围特别臭,是海鲜,估计很多人带,才会臭成这个程度。我这才想起来,白来一次大连,居然连只螃蟹也没有带回去。

16:13,高铁出发,大连至长春。因为返程的时间先前不能确定,需要根据老师指导的时间来定。所以,我离开大连理工的时候通知二猫妈,请网上定票,然后我到火车站打印车票。一等座,终于不再受那挤着的苦。

又3个小时左右的阅读时间,还是CSAPP,西方哲学史,天行健第二部,轮着看了一圈。窗外西沉的落日照亮原野和湿地,蓝天惭惭布满淡淡的云,然后慢慢黑了下去。我看着书页,眼皮开始打架。旁边的丝袜女在大声讲电话,"哎,你说我是不是有病啊,这两天我一听他说话就恶心呢,想吐"。我迷迷糊糊地要睡着的时候,在心里回答她,你可能真是有病,如果你打电话声音小点...后面的不再记得,因为我睡过去了。

19:58,长春。又1个小时的轻轨,21:00,我到家了。

早晨6:30左右出发,晚21:00返回。这中间,6个小时的火车,2个小时的轻轨,1个小时的出租车,半个小时的狂奔找路。我担心过这种强度腰间盘能不能扛得住,实践表明,戴上硬质护腰,虽然捂出不少汗,但是挺过来了。出发前跟董同学电话,他说,你一人在外,要小心啊。事实上,我确实恐惧。恐惧,可能是所有因素中,最令我最强烈地感觉到现实与理想的差距的。

曾经,我小学的时候吧,在地图上从通化画出一条线。先到长春,再到北京,再到祖国和世界的各个地方。直到大学入学,我第一次离开通化。那个时候,那么期待看到外面的世界,我从来没有想到,有一天,我会畏惧旅行。那个时候,年轻的我想的只有,"抬脚就走,有什么可怕。"那个时候,我不能理解更年长的人恐惧的来源,以为他们只是胆怯。那个时候,我也没有想到,所有这些城市,几乎都一样的建筑,一样地掩埋在灰尘雾霾之中。这座城市与那座城市也没有什么区别,你行万里路,看到的也只是相同的立面和街灯。

我也没有预料到,就像所有对未来充满美丽的期待的少年一样,我没有预料到,即使交通如何发达,即使在同一个城市,甚至在同一个校园,相距也一样遥远。人生不相见,动如参与商。生存的压力如此巨大,令我们不能相见相聚。甚至没有时间相互怀念。

--------------------

博客会手工同步到以下地址:



20130911

以前能跑步的时候

以前能跑步的时候

我从来没有想到过,自己也会有这样的时候。想提某一个人名,明星之类的,半天想不起来,虽然他的事迹已经在心里

转了两三圈。明星们的名字和青春的脸,最后的一批,就是刘德华、关芝琳、林青霞、张学友他们,剩下的,都是新人

。以前记得的事情,就像刻下了一样,永远记住了;新近出现的人和事,也像是刻下了,只不过是刻在了将融化的巧克

力上,转瞬即逝。

但是我确实看到了时光的流逝。十年,不过弹指一挥。弹指一挥这样的词,古人诚不我欺。十年前的事情怎样一一发生

和推进,我还记得清清楚楚有如昨天,但是十年前的毛头小子都成熟得能教我讲政治了,而他们在我心里,仍然是十年

前的样子。这真令我感叹。

前几天半夜的时候去跑步了,跑了一千米左右,速度也不怎么快。但是中途的时候膝盖就开始疼,里面隐隐地有针在扎

,虽然能坚持,但是最让我担心的是我知道有些损伤是单向的,不可恢复。年轻的时候,以为只要努力,什么都能坚持

过去,以为大不了一死而已,却不知道其实死没那么容易,活着却是更难。当事已如此,还要眼睁睁看着它继续,是件

折磨人的事。

那天跟关同学提起膝盖疼的事,她说,像你这样的就不能再跑那么远了。我说,哪么远啊,那以前跑一千米是达标要求

,都得能跑下来,那能有多远。关同学一笑,说:老师,你跑一千米的时候是多大岁数的事了。
我只有呵呵。


不达到锻炼强度就抑郁,提高强度各个关节就开始声明自己的存在。这让我想起打帝国时代的时候,你缺黄金,也缺粮

食,也缺石头和木头,更糟糕的是,你还缺军事力量,因为敌人打来了。

非常怀念当年能跑的时候。我所谓的能跑,并非有多么快或者持久,而是如孙同学所说,"你们帝国时代打得水平实在

不怎么的,但是玩得挺乐。"

我记得早晨四五点钟,冬天,天还完全没有亮,呼出的白气能有一米多长,空气刺痛肺子。跑,只注意呼吸,也只能听

到自己的呼吸和脚步,上坡下坡,有远远的车灯射到雾里面。很快就会喘不过气来,然后坚持,再坚持,等脑袋上全是

汗的时候,呼吸也开始顺畅。我看到巨大的月亮向西山落去,就像晨雾后面隐隐透出光来的初升的太阳。我有点迷惑的

停下来看的时候,脸上有开始结冰一样的感觉,风呼呼地吹过耳包,眼镜上一片迷蒙。有人从我身边飞快地超过,大声

喊我跟上。他的步幅非常大,每步都像弓箭步一样,同时极有弹性,没有一丝拖泥带水。他穿着像衬衣那么薄的衣服,

大腿在踏地的时候有力抖动。事实上,他穿的是运动服,那个时候我还不太认识,或者说,在我看来二者的区别在那个

时代也不怎么明显。

我跟了一段,在一个上坡慢慢落后,然后眼睁睁看他消失在山坡另一面,就像船帆没入大海。其实,我还记得与他的简

短对话,但是内容并不重要,大致是:快跑啊,越快就越不累,诸如此类的。

之所以后回忆起这一位,以后再也没有见过,或者擦肩而过没几句对话,因为在冬天的早晨,在这样的灰色弯曲上升的

马路上,我几乎没有遇到过旅伴。大部分人还在酣睡,特别早起的是做豆腐的,他们两三点的时候已经开始工作。路上

行人极其稀少,我还见过几辆牛车马车,比遇到跑步的人还少。

而同是跑步的人,竟然没有一个与你的速度、路线和方向完全一致。所以到了一定的青春年岁才开始感慨孤独的孩子,

一定是没有在盘山路上晨跑的经历。天很黑,你很累,路很长。这让人如何不绝望。

那个时候,我兴冲冲地跑完,开始打一本钉在墙上的书。有几个手指节就是在那个时候偏向了一侧,紧握拳的时候并不

突出肌腱,击打的时候接触面以指骨为主。但是这从来也没有用在打架上,完全辜负了我年轻的期待。倒是我妈很有意

见,说打得墙咣咣的,让人睡不好觉。再就是那个时候指关节受伤,我姥把她辛苦养的像仙人掌那类的什么花送给我妈

,说这能治我的手。

那个时候,跑一圈下来累得要死。那个时候,没有想到有一天会怀念能跑的日子。我突然想起前两天看齐同学讨论
那么

多的抉择和苦闷的时候,我说,这些以后都是财富,是以后吹牛的资本,不然,到了年老的时候,拿什么出来吹,说我

当年很牛呢。

读 赠卫八处士,听肖邦夜曲C小调第21。此时的夏夜,抬头当能看到浅淡的云,还有深蓝到泛黑的天空。佛陀感叹过人

生几苦,求不得,怨憎会,爱别离。也许,正是失去,也只有失去,才让我们感受到它清晰地存在。

人生苦短,世事无常,流沙过指隙。

20130830

对C语言的写文件操作fwrite的一个初学者常见误解

对C语言的写文件操作fwrite的一个初学者常见误解

当初对C语言的 写文件操作 到底会有什么样的结果很困惑,今天读CSAPP的时候又把这段回忆勾起来了。以下,希望能对如当年我一样的同学们理解fwrite有点帮助。

1.题外话,文件的重要意义

教科书中一般都会提到C语言的文件有这么个特色,它把所有设备都看成相同的东西,并称这是个优势。有的同学可能会奇怪,这有什么可提的。这种优点是教科书的作者和教师们从更早的书里抄来的,更早的书是对当时的情况发表的看法。在UNIX系统和C语言以前,操作系统处于一个更萌芽和早期的状态,对不同的设备的操作都使用专门方法--你可以理解为各有单独的函数。试想,键盘、鼠标、显示器、打印机、磁带、磁盘、磁鼓,所有这些东西都要作读写操作,而读写操作从人类的视角看来如此之像,却使用全然不同的函数和参数。UNIX和C语言改变了这一点,它把所有的设备"抽象"为文件,对所有设备的操作,都用相同的一组函数,即文件读写,来完成。这些各种各样的文件当中,也包括目录,所以目录也是一种文件。网络socket也是一种文件,进程间通信,也是一种文件。当很多不同的东西都统一于文件的时候,它们的个性就抹杀掉了,容易管理和控制多了。

因为吾生也晚,咱们已经习惯于这个格局了,就认为用文件管理所有的东西是天经地义的事情了,所以难以感觉到诸侯割据各自为政时的不方便。

2.问题

当我们读文件的时候,事情相对简单。打开,然后读,然后关闭。我们读到的正是我们期待读到的东西。当我们写文件的时候,情况就不同了。

常见出现的错误是,我们可以有个文件,内容是:

abcdefghijlmn

我们的C代码是:

打开文件, (可能在中间某个位置) 写操作,关闭文件。

执行完C程序以后,我们发现文件的内容不是我们期待的结果。我们原本期待中间某处变成我们修改以后的样,比如:
abcAAfghijlmn。

但是文件的内容却变成了:

^@^@^@AA

3. 原因

造成上面的问题,其原因肯定不是"因为微软的编译器太垃圾了",而是我们没有仔细阅读手册。

我们错误地假想,C语言操作文件流 (流,也是个UNIX语境下的重要概念),就像操作内存里的数组一样,找开文件就是找到数组的头 (起始位置),写操作就是改变数组中某处的元素。

但是事情并不是这样。世界并非如此简单。把文件作为流操作,那是文件打开以后的事情,在文件打开的时候,非常重要地,程序员必须指出,准备创建或打开一个什么样的流。

手册 (man fopen) 说:

fopen的第一个参数是文件名,第二个参数需要我们注意。第二个参数称为 mode,我们可以理解为创造的流的"模式"。

其中对"w"这种模式的解释的第一句是:

"Truncate file to zero length or create text file for writing."

truncate的意思是"截断"。上述这句可以译为:把文件截断为0长度,或者创造用于写操作的文本文件。这里插一句,在UNIX/POSIX系统下,文本文件与二进制文件没有区别。所以,"w"模式所创造的流是,要么如果原来这个名字的文件已经存在,把它截断为0字节,无论里面有什么内容;要么如果原来没有这个文件,创造一个新的文件。

前面问题一节里的文件,原本是:

abcdefghijlmn

我们期待:

abcAAfghijlmn

但是却变成了:

^@^@^@AA

1.AA以后的东西会丢失,就是因为原来的文件被trancate到了0长度. 2."^@"是单独一个字符,即'\0',这是由于我们的写操作是向某个特定位置进行造成的洞.

4. 解决

也许我们这些唯物主义者关注一下唯心的书,可能理解这个问题更容易一些。康德的《纯粹理性批判》、哈耶克的《科学的反革命》,还有 Design of Everyday Things,都提到一个观点。我们对于所有事物的理解和理解发以后的操作,都是基于心中的一个"模型",或者说,我们认为它会 (或者应该) 那样工作。

如果它不是那样工作的,我们会说建模有问题。不过这个字眼很学术味。它的意思大致等同于,那玩意根本就不是你想的那么回事。对于fwritet这个具体问题而言,它不是如我们误以为的数组这样的流,而是在fopen时决定了,会对文件系统有些副作用的操作,然后创造了流中的某一种。

我们想要的效果,需要用以下方法解决.

FILE * f = fopen ("test.in", "r+");
// 注意此处不是 FILE * f = fopen ("test.in", "w");
// w模式会把文件内容清空
// r+这种模式的意思是:
// Open for reading and writing.  The stream is positioned at the beginning of the file.

5. 补充个无关的,sizeof

char output[3] = "AA"; /* sizeof -> 3, the number of the array elements */

对数组sizeof操作 (其实sizeof不是函数,而是关键字),得到的是数级中元素的个数.

char* output = "AA";  /\* sizeof -> 4, the size of pointer type *\/ */

对指针sizeof操作,得到的是指针这一数据类型 (不是指针的基类型)的长度,指针在32位系统中是4字节。

6. 代码

6.1 覆盖文件内容的写操作

test.in的文件内容:
abcdefghijk

执行结果为:

^@^@^@AA

或者十六进制形式为:
$ hexdump -C test.in
00000000  00 00 00 41 41                                    |...AA|
00000005

C代码:
#include <stdio.h>
int main(int argc, char *argv[])
{
    char output[] = "AA";
    //FILE * f = fopen ("test.in", "r+");
    FILE * f = fopen ("test.in", "w");
    fseek(f, 3, SEEK_SET);
    fwrite(output, sizeof (output)-1, 1, f);
    fclose (f);
    return 0;
}

6.2 替换文件内容的写操作

test.in的文件内容:
abcdefghijk

执行结果为:

abAAefghijk

C代码:
#include <stdio.h>
int main(int argc, char *argv[])
{
    char output[] = "AA";
    FILE * f = fopen ("test.in", "r+");
    //FILE * f = fopen ("test.in", "w");
    fseek(f, 3, SEEK_SET);
    fwrite(output, sizeof (output)-1, 1, f);
    fclose (f);
    return 0;
}

7. 致谢

想起当年从BASIC语言向C语言迁移时,师兄们的教导令我受益良多。感谢张仕鹏师兄,还有一位一时名字没想起来的师兄,还有于寅虎师兄。恩,还有灌我酒的唐猛师兄,通过TC的BGI教会了我指针。

--------------------

博客会手工同步到以下地址:



20130829

用windows sdk写一个贪吃蛇

用windows sdk写一个贪吃蛇

这是上学期程序设计与实践3课程的一部分,用windows sdk写一个贪吃蛇。此项目由教师在课堂上演示、查SDK手册和解释、design和实现及解释代码,学生观摩。一共进行了五次课。持续五周,每周一次课,每次课90分钟。

1. 原由

之所以选用 windows sdk的原因如下。1.我的学生刚好学了一部分 windows sdk,虽然sdk更难,但是对于我的学生来说,比MFC却要简单。原因很明显,因为MFC无论有多么好,他们没学过。同理,.net也不如sdk适合。有的时候用户需求就是这样诡异,而你只能随着用户的知识结构调整自己,而不是反过来。2.java awt/swing同学们倒是学过,但是大家以后用java做gui的机会比较少。3.虽然不少人以后可能会做前端,但是用java script之类的也不合适,理由也是他们没学过。教师不能把所有的课程都变成 补基础知识的课程。只能因陋就简了。

选sdk的另方面的原因是希望通过贪吃蛇训练学生:1.即使没有学过某种知识(比如画图、音乐、刷新GDI等等),仍然能通过查手册建立技术原型;2.了解消息循环。

没有学习过,但是却可以自学,是一项重要的能力。假期的时候王师兄提到他遇到的一位女导师,似乎是用GTK从VC移植界面,花了两个 (?)小时就完成了。问她以前学过吗,她答:没,看看手册,然后就做出来了。我们希望培训学生这样的能力。我得承认,这很难,对教师比学生还要难。所以此贴谨供有此理想的教师和同学参考,并热烈期待指导和意见,而此贴本身绝非指导意见。

2. 流程

一般的软件工程 (和/或示范)都假设同学 (或工程师)已经具备和掌握了相关技术知识,对于底层机制的调用是routine类的工作。在本案例中,由于同学们对sdk能做些什么尚无完善的认识,因此大体的流程与通常的软件工程有所不同。我们先做简单的需求分析,估计有哪些功能是我们需要的,然后通过google和手册到sdk中找,找到了以后要做实验,证明这个功能是我们可以实现的,最后再把这些功能的代码在最后要用的项目中写一次。

这有点类似于快速原型法,但是动机不同。快速原型法的目的是确保软件工程师对用户的需求理解无误,用呈现出来的方式与用户确认。我们做原型的目的,是确保SDK这样的底层机制确实支持你要使用的功能。这对于初学者或者探索类的项目具有格外重要的意义。

3. 技术原型

把在需求非常粗糙的时候,先写出来的一些小程序,用于验证底层机制 (如SDK)支持这样的功能,这种小程序我们估且称为技术原型。

在课堂上,我们认为贪吃蛇有以下这样几个技术原型需要确认。

(1)画一些线,组成矩形; (2)把矩形擦掉; (3)把擦和重画结合起来,形成动画效果; (4)不阻塞地获取按键的状态,即程序不停,而在按键时做出某种影响。

前三种技术原型在写程序之前,先有"手动"的演示。教师在画图工具上,画出矩形,然后擦、画,形成动画效果,同时简单介绍视觉暂留现象。

按键获取这一技术原型,教师指出 getchar 或 scanf 这一类的函数不适合的原因,然后引入消息循环的概念。

4. 技术原型的补充

在开发的过程中,又发现需要 timer 的技术原型。把主项目停下来,写 timer技术原型,验证之后,把代码复制和修改到主项目中。

在开发中,发现当初考虑得不周到,有未尽的技术原型,这是正常的现象。除非你正完成的是与既有工作没大差别的项目,未计划周全是正常的。这也是教师在课堂写程序同时学生观摩的意义之一,使学生认识到"错误"不可避免,同时重要的是遇到"错误"如何修正。教师在课前把代码写好调好,课堂上按下F5就运行,同学们无法看到教师修正错误的过程,而这一过程会是他们写下最初100行时最需要的经验。

5. 对技术原型的再讨论

每个原型都在200行左右,除去由wizard生成的消息循环代码,就更短了。这在学生注意力和理解力可达的范围内。如果以整个工程的面目示人,则学生就需要从大的背景中找出某种技术对应的代码来,这就难多了,学生更容易产生挫败感。

每个原型都比较短,即使加上教师课堂上解释的时间,读文档的时间,一般地一到半次到两次课也能够完成。从无到有,虽然只是实现了一个小功能,但是如果学生事先就知道这一功能将出现在最后的工程中,这点小成就还是可以让他们小小地满足一下。

这些原型除了课堂演示,还要求同学们作为作业在当周完成。观摩教师写代码,和自己亲手历经各种困难把自己在课堂上已经见到效果的小项目重现出来,课程实践表明,难度上还是有很大差距的。事实上,全程能基本跟下来所有原型实验的同学,只有一名。

另,把技术原型中的代码抄到主工程中的时候,教师强调 业务逻辑与技术原型的分离。业务逻辑是尽可能与所依赖的平台和技术原型无关的。当然,类似于消息循环和按键消息响应这一类的技术原型,不可避免地与框架相关联。好在,这样的技术已经可以在相当程度上视为通用技术了,在很多平台上都类似。

6. 抽象

利用原型已经验证过的那些api,代码半抄半改,形成了整个贪吃蛇项目。当程序能跑起来没有bug的时候,同学们非常兴奋。我记得当时大家还鼓掌喊"oh yeah"了。

对于软件工程想要贯彻地教学目标而言,这才只是开始,如何抽象,比如用面向对象,比如把重构extract函数,这些才是真正的动机。而这些内容的教学,可以建立在上述刚刚由大家一起完成的贪吃蛇项目中。

在课堂中,我提出"需求变更",蛇头的颜色要求不同,碰撞的规则的变更,要求有更多的关卡等等。有些需求变更的满足,演示了extract函数,比如蛇头的颜色;有些要求重新设计数据结构 (结构体) ,变修改访问过这些结构体的函数;要求更多关卡这个,则演示了DSL的代码生成技术,我们又写了一个程序,专门用于把ASCII艺术画方式实现的关卡地图转换为C代码。比如下面几个关卡中,用"*"代表有障碍物的地方,"."代表空白处:

......
......
......
......*
.....**
......*
......*
......*
......*
.....***
------------------------------------
.
.
.
.
....*******
..........*
....*******
....*
....*******
-----------------------------------
.
.
.
.
....*******
..........*
....*******
..........*
....*******
-----------------------------------
.
.
.
.
....*.....*
....*.....*.
....*******
..........*
..........*
-----------------------------------
.
.
.
.
....*******
....*.....
....*******
..........*
....*******

上述关卡会由代码生成器转换成这样的代码: 
#define MAX_BLOCK_LENGTH 100
int block_length_stage[5] = {0, 10,23,23,13 };
int block_x_stage[5][MAX_BLOCK_LENGTH]={{0},
{6, 5, 6, 6, 6, 6, 6, 5, 6, 7},
{4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10, 4, 4, 5, 6, 7, 8, 9, 10},
{4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10, 10, 4, 5, 6, 7, 8, 9, 10},
{4, 10, 4, 10, 4, 5, 6, 7, 8, 9, 10, 10, 10}
};
int block_y_stage[5][MAX_BLOCK_LENGTH]={{0},
{3, 4, 4, 5, 6, 7, 8, 9, 9, 9},
{4, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8},
{4, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8},
{4, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8}
};

关卡生成器也放在了附件中。

7. 未尽之事

最后的结果并非漂亮的设计,课堂中我也提到,真正的工程一般不会给我们机会做这么多地技术原型--它表明工程师的技术仍不成熟。但是,技术不成熟如果是事实的话,那么事实只能通过弥补来减弱影响,而不可能通过掩盖来避免负面效果。所以,如果学生或工程师技术不成熟,技术原型是一个帮助的工具。我们不能因为工具会暴露人类在体力上相对其他动物不足而拒绝使用。

代码也并非漂亮的代码,事实上,有很多不尽人意的地方。这些地方未进行修改的原因,一是我作为教师确实能力有限,对SDK的了解远不全面和深刻,二是它们超出了我原定的教学目的。

8. 感谢

我上课的时候犯了一个错误,没有先建立版本控制就开始写代码,每次课后备份的时候都覆盖了前次的代码。最后的结果固然还在,但是中间的过程也是我希望呈现的,不可或缺。

好在尤其同学备份了每次的代码,并非未如我一般愚蠢地操作,所以中间过程得以保留。所不完满者,尤其同学只备份了代码,而非整个工程,虽然保留了核心的代码,但是需要一些VC的使用知识才能编译。

9. 附件的目录树

|-- draw 绘制矩形的技术原型
|-- erase 擦除矩形的技术原型
|-- keyup 按键检测的技术原型
|-- from_YOUQI 尤其同学给出的有版本控制的代码
|   |-- week10
|   |   |-- draw
|   |   `-- erase
|   |-- week11
|   |   |-- keyup
|   |   `-- snake
|   |-- week12
|   |   |-- snak_business_logic_imp.h
|   |   `-- snake.cpp
|   |-- week13
|   |   |-- snak_business_logic_imp.h
|   |   `-- snake.cpp
|   `-- week14
|       |-- gamedesigner.cpp
|       |-- run.bat
|       |-- snak_business_logic_imp.h
|       |-- snake.cpp
|       `-- stage.txt
`-- snake
    |-- gamedesigner 关卡生成器
    |   |-- Debug
    |   |   |-- run.bat 脚本/批处理,用于把stage.txt转换为stage.h
    |   |   `-- stage.txt 关卡定义的源文件,关卡生成器的源文件
    |-- snake
    |   |-- snak_business_logic_imp.h 业务逻辑
    |   |-- snake.cpp 消息循环框架
    |   |-- stage.h 关卡生成器的输出文件


我无法通过GFW,因此不能向blogspot传附件,请参见[http://download.csdn.net/detail/younggift/6029671]

--------------------

博客会手工同步到以下地址:



20130828

今天暑假我做了什么

今天暑假我做了什么

今天暑假是我从初中二年级开始,唯一算得上假期的一个夏天。初二开始,假期不是各种学校要求的补习,就是竞赛,反正没有消停时候,连初中毕业、高中毕业、大学毕业后的假期也没歇着。今年假期算是例外了。

暑假原计划去趟通化老家,再跟好朋友去旅游一下。结果,刚放假,老猫就病了。它屁股上长了青春痘一类的东西 (学名肛门腺) ,烂了个洞,得天天上药。所以人类也无法远行。我本以为这是给猫上刑,结果猫妈准备了老猫酷爱的妙鲜包,每天上刑的时候老猫都屁颠屁颠地往前凑。

即然走不了,就只好读书。暑假在继续读没看完的几本,有进度的包括:CSAPP,实时系统,历史理性的重建,Algorithmic Adventures,On Lisp, The Design of Everyday things。及一些乱七八糟的东西。以上都没看完。

另一个重要收获是假期快结束的时候开始打星际争霸任务。星际争霸是1998年的老游戏,那个时候windows2000也还没出世,更不用说xp。显示器那时都是4:3的,所以这款老游戏不支持宽屏,得先调成窄屏然后才能玩。插话评论一下,我一直震惊于有些人在宽屏上看普屏的电视节目,人压缩得矮胖,但是他们还是能看下去。星际争霸似乎是640*480的如此之低的分辨率,更不用说好象还是16分游戏,不是16位,而是16种颜色。平衡性什么的,这么有名气的事情,我就不赘述了。任务的故事性挺强,如看美国大片。欺骗与背叛,情理和公义,隐约的爱情,古老家族 (或者贵族?)的腐烂,你能想到的基本都有。更难得的是身在其间,感觉格外不同。

这游戏给我最大的启发是:如果需要训练什么技能,一定要尽早。这道理我最初是在李笑来先生的《把时间当作朋友》里看到的。他说,如果很早就会五笔的话,一定能比别人多记很多笔记;如果很早学会快速阅读,一定能比别人多读很多书。大致如此,不是原意,李笑来先生反对五笔和快速阅读这种东西。我的体会,如果发展徒手攻击力需要100矿+100气的话,那么升级比生产同样价格的兵要有价值得多。因为它会使你手头所有的兵及今后的兵都是更有价值的。

唯一需要考虑的前提就是,如果当前能活下去。所以,只要现在还能对付活下去,那么,发展未来远比现在重要。我们活在未来,而不是此刻。

不那么幸运的就是在星际争霸里,正打得顺风顺水,突然被翻盘的可能性太大了。敌人会根据你的兵种情况生产克星。所以,经常性地了解对手的发展情况,对我们来说也非常重要。

再就是,有些任务的关卡实在太难打了,简直不是人类能通过的。可能一,咱们技艺就是不行。虫族最后一关,我要花近两个小时,而网上录像我看到十几分钟推平敌人的,有的是。可能二,任务的目标根本不是打平天下,而是在一片战火中送个人到什么地方去--这相对容易多了。所以,充分了解用户需求是多么地重要啊。

暑假最重要的收获,我真正地成为了物理系毕业的学生。不是指同学聚会,我终于能用筷子起开啤酒了。这是所有物理男的必修课,今夏补考通过。有窍门,以后再讲给你们听。

这个奢侈地暑假,你做了些什么?

--------------------

博客会手工同步到以下地址:



20130818

同学聚会

同学聚会

今天假期的一件大事,就是大学同学要聚会,早早就把时间定下来,别的事情都为聚会让路。

我们年级一共95多人,此次回来50多个,大家在一起吃睡玩了几天。作息一致,就是大学时的感觉吧。有些同学已经叫不出名字了,有的同学干脆就完全没有印象。大多数同学,基本没有什么变化。也许变老了些,性格上基本都是以前的强化或者加些世俗的因素。

一般都问问职业。不少人从事IT相关的行业。我第一次听到这个词还愣了一下。IT,这是当年还没有的词汇。15年来,产生了不少新词,世界变化真大啊。

我原以为15年来自己成长成熟了很多,兴冲冲地准备向大家汇报。结果大家说的话里的机锋,我仍然得别人背地里解释了才能明白。显然,我虽然进步了,但是仍然跟不上大家的脚步。

2013夏天似乎是聚会高峰,我听说地看到的不少大小聚会。也许,2012过完了,我们终于认识到仍然活着该有多么宝贵。所以得聚聚了,以示珍惜。

在我们去物理系认养松树那天,我早到了,看到门口挂的条幅写着89级同学聚会。我想,系里这也太不靠谱了,核计着找谁联系换一下。正想着,从门里哗啦啦出来一批老头老太太 (和我的同学比)。原来,真的是89级同学在聚会。后来在东师会馆等同学,一位男士径直走到我面前热烈握手,自报家门说他是谁谁谁。我说:师兄你好,我不是你们那届的。

今天夏天,还有师兄回来长春,我跟着师兄的同学 (我的师兄们及宝贵的师姐1人) 聚了一次,又单独谈了两个下午。他给我讲了科学问题是什么意思,我收获很大。


--------------------

博客会手工同步到以下地址:



20130723

解密:LL与LR解析 2(译,完结)

由于GFW,我无法联系到作者,所以没有授权,瞎翻译的。原文在这里[http://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html]。

这是第2部分和完结。

3. 解析树的形状


到目前为止,我们使用的算术表达式的那棵树,仍然不是解析树,因为它并未与语法关联。要考查一棵真正的解析树,我们需要语法。不幸的是,为中缀算术表达式写语法不像你期待的那么简单和优雅。对优先级和结合性 (杨注:操作符左结合还是右结合)编码,保证语法没有二义性 (并受LL和LR支持) ,是非常丑陋和不符合直觉的。这也是为什么LL和LR解析器也允许你做指定操作符优先级这样的扩展;比如,参见Bison优先级的相关特性[http://www.gnu.org/software/bison/manual/html_node/Precedence.html#Precedence]。而这篇文章的目的是打算讨论纯的LL和LR。

因此,我们得把那个算术表达式的例子调整为比较容易写的语法的形式。我们将使用JSON (杨注:JSON是javascript的对象表示方法) ,既然它非常简单,而又足够复杂和有趣。

1 object → '{' pairs '}'
2  
3 pairs → pair pairs_tail | ε
4 pair → STRING ':' value
5 pairs_tail → ',' pairs | ε
6  
7 value → STRING | NUMBER | 'true' | 'false' | 'null' | object | array
8 array → '[' elements ']'
9  
10 elements → value elements_tail | ε
11 elements_tail → ',' elements | ε


上面,我用了单引号括起的字符串表示 原文文字标记 (literal tokens),用大写字母,比如STRING,表示那些拼写不确定的tokens (比如,"abc"和""都是有效的STRING tokens)。所有的名字小写字母的,都是语法规则 (也称 非叶节点)。

你可能奇怪,为什么我要用 pairs_tail 和 elements_tail,而不用重复构造 (repetition construct) ,像很多解析器比如ANTLR支持的那样。这样,我们就可以这样写:

elements → value (',' value)*

使用*的这种语法,用起来更方便,语法也更简单,但是同时,它导致解析树概念上更复杂了一点,因为某个给定的语法规则的子树个数不再是确定不变的。并且,LR解析器不支持重复操作符(比如,Bison就不支持),这样,我上面写的语法就既可以用于LL也可以用于LR解析器。因此,我们现在要使用这个有点复杂的语法。

现在,我们有语法了,那么我们来看一个token的流的例子,再来看输出的解析树。

{"message":"Hello, World!"}

上述这段文字的token流是:

{ STRING : STRING }

而它的解析树,按我们的语法,就是:



注意,所有的叶结点 (绿色的)都是tokens,它们的顺序与我们的解析器的输入顺序是完全一致的。 (我做了一点小弊,把ε作为叶结点了,不过正如我们所看到的,这看起来更干净更规则一些)

我前面曾经断言过,LL解析器输出的是先序遍历,而LR解析器输出的是后序遍历。从这一点出发,我们可以知道LL和LR解析器对上述输入分别会给出什么输出:



既然叶节点总是输入的tokens本身,且完全按输入的顺序,所以所有的解析器真正所做的,就是把中间节点 (杨注:语法规则)插入到合适的位置。看这一点的另一个角度就是,一棵解析树,就是一堆结构体,这堆结构体定义在输入的tokens的序列之上。我们稍微重新安排一下之前的这个图示,这一点看起来就更清楚了。
 



我们正集中讨论一个非常简单的模型,用这个模型描述LL和LR解析器如何工作。LL和LR解析器二者都读入一个输入tokens的流,再把相同的流作为输出,并且把规则 (杨注:中间节点)插入到适当的位置,以形成解析树的先序 (LL)或后序 (LR)遍历。




这样,按波兰和逆波兰表示法考虑,这种对解析器输出的认识又带给我们一个好处。它使得我们可以对解析器的输入和输出都按简单的、平坦的流建模。这比把解析器的中间输出状态视为部分地构造树要容易多了,那种思路对于理解输出和对输出的检验都没什么帮助。

4. 超前 (Lookahead)

LL和LR解析器都是"在线的",意味着它们都能在输入正在进行时开始产生输出.  但是在许多情况下,在流的位置之前的tokens没有包含足够的信息,因此解析无法知道是否需要插入规则 (或者,如果需要插入规则,应该插入哪一条).因此,解析器得超前 (lookahead)到流的后面,看看下面的一些tokens是什么,以便做出决定。当你看到像LL(1)或者LR (0)这样的命令的时候,括号里的数字就是要超前的tokens的数量。

值得注意的是,超前是相对于规则将要插入的位置而言的,这个位置 (正如你记得的)对于LL解析器而言是在规则的tokens之前,而在LR解析器的规则tokens之后。这意味着,LL超前从规则的tokens的开头开始计数,LR从末尾开始计数。这带给LR解析器一个巨大的益处,因为在它们做出决定之前,他们能够看到规则的所有tokens (可能再超前一些),而LL解析器只能看到规则最初的几个tokens。



这就是为什么会有LR(0)解析器这种东西,而LL(0)解析器是不可能存在的;那样就根本不会有信息用来帮助决定接下来的tokens应该使用哪条规则。


5. 结果

根据上述对于LL和LR解析的比较的理解,我们能够得到几条重要的结论,有助于理解为什么有些当然的事是那样的。

(1) LR解析器能够处理更多的语法

这一点可由上一节超前 (lookahead)推得。既然LR超前开始于规则的末尾,在做决定的时候,LR(1)就确定地比LL(1)拥有更多的信息。进而,LR(1) 解析器确定地能比LL(1)解析器多解析一些语法 (杨注:原文接下来在括号里是modulo LL-only grammar extensions; see below。我不知道什么意思)。LR解析器可以处理左递归,LL解析器不能。

优势:LR

(2) (杨注:EBNF这一类的)

另一方面,既然LL解析器在开始解析规则的tokens之前就选定了使用哪条规则,并且无论LL解析器什么时候解析一个token的时候,它一定知道其token的上下文。这是一个更困难的任务 (既然它们拥有的能够继续的信息更少),这导致了一些重要的优势。

LL解析器在语法中能支持 像正则表达式 一样的操作符。

知道解析的上下文,这使得利用正则表达式形式的多种多样的操作符成为可能,比如重复 (杨注:*),比如alternation (杨注:|),而且可以用在任何地方,而不仅仅是顶层处。基本上,每条规则都能构成一个DFA状态机。对于自顶向下的解析,这是可能的,因为解析器知道它位于哪条规则之中,在解析进行的过程中可以按规则的状态机进行。我认为这对于自底向上的解析,这是不可能的 (甚至如果你以某种方法令解析表做正确的事,归约那一步也需要归约有固定不变的参数个数。杨注:不懂)。这对于LL真是个好优点,因为有这些丰富的语法扩展(杨注:指类似正则表达式的),语法容易读多了。事实上,这有利于使LL那种严格语法的局面有所缓和,因为许多你需要左递归的地方都可以使用重复 (*)操作符替代。

1 // LR语法: 不允许任何特殊的,alternation 只允许
2 // 在顶层出现
3 //
4 // 允许这一条是因为它等价于
5 // pairs → pair pairs_tail
6 // pairs → ε
7 pairs → pair pairs_tail | ε
8  
9 // 扩展的LL语法; 之所以可能,是因为你可以对把每条规则
10 // 构造成一个DFA
11 pairs → (pair (',' pair)*)?

后一条规则可以构造出像这样的DFA (绿色的状态表示接受状态) :



知道上下文,也使得在规则中间的动作成为可能 (定制代码,这些代码运行在规则里的任意两个元素之间。杨注:如antlr的 semantic action)。Bison支持这一点,是通过在内部重写了语法,这使得所有的可视化 (杨注:可能指语法定义的时候?)都更加复杂了。

优势:LL

(3) LL解析器支持上下文相关的扫描/词法分析

知道上下文,另一个好处是也使得上下文相关的扫描/词法分析成为可能。比如,许多程序设计语言不允许把关键词用于变量名,因为独立的词法分析器 (及自底向上的解析器)不知道出现在这个位置上的token是变量名还是关键字。但是自顶向下的解析器调用词法解析器的时候,可以轻易地把当前的上下文传递给它。

优势:LL

(4) LL解析器支持继承属性

知道上下文,也能够支持基于LL的应用程序在构造树的时候把属性/元数据传递给树 (这有时被称为继承属性。杨注原文:inherited attribute)。 (无论LL还是LR解析器都支持综合属性 (杨注:原文synthesized attributes),是由树向上传递的)。

优势:LL

6. 结论

我描述了一种另类的LL和LR解析器的模型,这种模型与大多数文献中提到的等价,但是更符合直觉 (至少对我而言是这样)。我们可以把解析器视为黑盒子,这个黑盒子输入输出与先序和后序表示法对应的token和规则的流。至目前为止,我们还没有探索这些解析器的内部工作原理;我们只是把它们视作黑盒,我们不清楚它们内部的工作。我们也没有探究它们能处理和不能处理何种语法的问题。我们也没有探索LL和LR的变形 (Strong-LL, SLR, LALR等等)。我希望在接下来的文章中会更完整地讨论它们,再包含上示例代码。

解密:LL与LR解析 1

解密:LL与LR解析

作者:Josh Haberman翻译:杨贵福

由于GFW,我无法联系到作者,所以没有授权,瞎翻译的。原文在这里[http://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html]。

2013年7月22日

我最初解析理论的经历来自大学时自学程序设计语言的时候。当我学到像LL,LR还有它们的变型 (比如Strong-LL, SLR, LALR等等)的时候,我迷惑了。我觉得正注视着的是艰深而强大的咒语,它的重要意义我尚不能领会,但是我确信,总有一天,像"从左至右导出""最右导出"这些术语会融汇贯通,于是我继续努力期待明白的一天。


现在我可以说,经过10年的时间再加上看了一整架解析类的书以后,我把这些算法理解得不错了。但是我看待它们的角度和我看过的文献都非常不同。我更多地从实现的角度,而不是数学的角度,数学的角度也起了一些作用 (杨注:瞎翻译的)。无论如何,我想解释一下我是如何看待这些算法的,希望有人也像我一样觉得这个角度更直观。

这篇文章只涉及到把解析器视为黑盒子这一角度:即解析器的输入/输出,及解析器的限制。后续的文章将打开黑盒子,把这些算法内部工作的更多的细节展示出来。

1. 解析 与 波兰表式法

如果你在大学学习计算机科学,或者甚至你要是有个惠普的计算器 (杨注:我从来没见过逆波兰的HP计算器,而且,空格在那上面如何表示啊?) ,你就见过波兰和逆波兰表示法。它们能不用符号,也不用四则运算顺序规则,就能写出数学运算表达式。我们习惯于把表达式写作中缀形式,在这种形式下,操作符置于操作数二者之间:

1 + 2 * 3

在这种形式下,你如何知道计算的优先级呢?你不得不按约定的规则 (四则混合运算的法则)。你如何想按不同的次邓,就必须用括号了,像这样:

1 (1 + 2) * 3

在波兰和逆波兰表示法中,你不必关心四则运算的优先级,也不必加括号,同样可以避免二义性。这是通过把操作符放在操作数之前(波兰表示法)或之后 (逆波兰表示法)实现的。它们也分别被称为前缀和后缀表示法。

// 第一个例子: 1 + 2 * 3 // 中缀+ 1 * 2 3 // 波兰表示法 (前缀) 1 2 3 * + // 逆波兰表示法 (后缀)
 
// 第二个例子: (1 + 2) * 3 // 中缀* + 1 2 3 // 波兰表示法 (前缀) 1 2 + 3 * // 逆波兰表示法 (后缀)


除了不需要括号,也不需要运算次序的约定以外,波兰和逆波兰表示法在写运算器 (求值)的时候也容易很多 (也许HP计算器的设计师用逆波兰表示法,就是为了能去巴哈马群岛度一周假) 。下面是一个Python实现的逆波兰的简单求值器。

1 # 函数定义了操作符,及如何依据操作符求值
2 # 本例假设操作符都是二值的,不过容易扩展为多值。
3 ops = {
4   "+": (lambda a, b: a + b),
5   "-": (lambda a, b: a - b)
6 }
7  
8 def eval(tokens):
9   stack = []
10  
11   for token in tokens:
12     if token in ops:
13       arg2 = stack.pop()
14       arg1 = stack.pop()
15       result = ops[token](arg1, arg2)
16       stack.append(result)
17     else:
18       stack.append(int(token))
19  
20   return stack.pop()
21  
22 print "Result:",  eval("7 2 3 + -".split())

波兰和逆波兰表示法,确实如通常所说的,需要事先知道所有操作符的参数数量。这里的参数数量,指的是操作符所作用的操作数的数量。这意味着,单值操作符负号和二值操作符减法,是两个不同的操作符。否则,我们在遇到操作符的时候,就不知道从栈中弹出多少个操作数。

一种避免了这个问题的类似表达方法,是Lisp语言的s-表达式。s-表达式 (还有类似的编码形式,比如XML)避免了固定操作符参数个数的需要,实现这一效果的方法是明确标记每个表达式的开始和结束之处。

1 ; Lisp风格的前缀表达式;
2 ; 同一个操作符可以有不同的参数数量
3 (+ 1 2)
4 (+ 1 2 3 4 5)
5
6 ; 我们前两个例子在Lisp中的等价表达方式
7 ; 前缀: + 1 * 2 3
8 (+ 1 (* 2 3))
9
10 ; 前缀: * + 1 2 3
11 (* (+ 1 2) 3)

Lisp这一表达法有不同于前述方法的妥协 (前面的方法中要使用固定数量的参数,Lisp需要括号),但是它们底层的解析/处理算法是非常类似的,因此通常我们把它们视为略有不同的前缀表达式。

看起来我好像有点跑题了,不过,其实我一直在偷偷地讨论LL和LR。按我的观点,LL和LR解析正分别与波兰和逆波兰表示法直接相关。不过为了完整地探索这个想法,我们需要先描述一下我们需要解析器输出什么。

作为一个有趣的练习,请尝试实现一个算法,用于把波兰表达式转化为逆波兰表达式。看看你是否可以不需要先把整个表式式转化为为一棵树;你可以只用一个栈实现这个效果。现在,比如你又要实现相反的过程 (从逆波兰到波兰)--你只需在输入上运行同一个算法,这回转换的方向就相反了。当然,你也可以构造一棵中间的树,但是这导致 O(输入长度) 的空间,而单使用一个栈的解决方案只需要 O(树的深度) 的空间。如何从中缀到后缀呢?有一个非常聪明和高效的算法,称为 调度场算法[http://en.wikipedia.org/wiki/Shunting-yard_algorithm]。

2. 解析器及输出

我们一致认可解析器的输入是token的一个流 (这个流极可能来自一个词法分析器,不过我们可以以后再讨论这一部分)。不过解析器的输出是什么?你可能倾向于说"一棵解析树"。当然你可以用解析器构造出一棵解析树,不过也可能不是这样,而是一种完全不构造解析树的输出。比如,这个Bison的例子[http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html#Infix-Calc] ,在解析的同时求值了算术表达式。每次当子表达式被识别出来,它立即被求值,直到最终的结果是一个单独的数。从来没有解析树显式地构造出来。

因此,说解析器的输出是一棵解析树不具有足够的一般性。相反地,我断言:解析器的输出,至少我们今天讨论的LL和LR的输出,是解析树的 *遍历*。


如果触动了哪位真理洁癖的神经,我在此道歉。我可以听到有人抗议道,树的遍历是一种算法,是你施加于一棵树上的操作。我怎么能说解析器输出了一棵树的遍历呢?答案在于,请回想一下刚才的波兰和逆波兰表式法。它们通常只是一种数学算式的表示法,不过我们也可以更一般性地把它们视为 对树的遍历的扁平和线性的 (序列化的)编码方式。


回想 下我们的第一个例子 1 + 2 * 3。下面是这个表达式的树形的写法:

    +
   / \
  1   *
     / \
    2   3



有三种方法遍历这个二叉树,如在维基百科上所给出的:中序遍历 (in-order) ,先序遍历 (pre-order) ,后序遍历 (post-order)。它们的不同只在于你访问父节点的时机,是在访问子节点之前 (先序),之后 (后序),或者左右子树之间(中序)。这三者正与中缀、波兰、逆波兰表示法对应。

1 + 2 * 3 // 中缀表达式,中序遍历+ 1 * 2 3 // 波兰 (前缀)表达式,先序遍历1 2 3 * + // 逆波兰 (后缀)表达式,后序遍历

所以,波兰和逆波兰表示法 完全地编码了一棵树结构,并且规定了你遍历它的步骤。在这些编码方法与一棵实际的解析树之间的主要区别,在于 波兰和逆波兰表示法 编码的访问并非随机的。对于一棵真实的树 (杨注:计算机里的真实,不是现实的真实,哈哈,所谓真实),你可以跟随一个内部节点到它的右子树,或者它的左子树,或者甚至 (对于许多树而言)它的父节点。在这些线性的编码方案中,就没有这种灵活性:你只能采用它已经这样编码了的那种遍历方法。

但是,好的一方面是,它使用解析树的输出是一个流,这个流是在解析行为发生的时候产生的。这也是Bison的那个例子,它如何在没有实现构造一棵树的情况下,就能够求值算术表达式。如果真的需要一棵不是扁平编码的树的话,从线性的树遍历中很容易就能构造出一棵来。不过,当不需要这棵真的树的话,构造它的代价就完全可以避免。

这就引出了关键点:

LL和LR解析器操作之主要不同在于,LL解析器输出解析树的先序遍历,而LR解析器输出后序遍历。

这等价于那些更传统,但是 (按我的观点)更易令人迷惑和不那么直观的关于区别的解释:

* "LL解析器产生一个最左导出,而LR解析器产生一个逆转最右导出。"

* "LL解析器自顶向下把树构造出来,而LR解析器自底向上构造。"

* LL解析器通常称为"带预测的解析器"(杨注:原文predictive parsers,这是不是有约定的翻译啊),而LR解析器称为归约解析器 (杨注:原文shift-reduce )。

今天先翻译到这里,原文后面还有。

20130721

昨天CSAPP上的疑问的解答

昨天CSAPP上的疑问的解答

今天整明白了。

CSAPP英文版第2版,826页,或者中文版第2版546页,有这么一段。关于多级页表的。

"But if we had a 32-bit address space, 4KB pages, and a 4-byte
PTE[page table entry, 杨注], then we would need a 4MB page table
resident in memory at all time..."

其中"32-bit address space"的意思是 2^32 bytes,而不是2^32 bits,因为内存是按字节而不是按比特寻址的。

根据公式:页表尺寸 = (地址空间 / 页尺寸) * PTE入口大小........公式1

32-bit address space: 2^32 bytes (昨天误作bits)
4KB pages: 4K bytes
a 4-byte: 4 bytes
B: bytes



K = 2^10
M = 2^22

代入公式1的右侧,得

(2^32 bytes / 4K bytes) * 4 bytes
= 2^32 * 2^2 / (2^2 * 2^10) bytes
= 2^22 bytes
= 2^2 M bytes
= 4MB

--------------------

博客会手工同步到以下地址:

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

20130720

你最近在读什么书,及CSAPP上的一个疑问

你最近在读什么书,及CSAPP上的一个疑问

"你最近在读什么书?"这句话我在两处看到过。一处是鲁迅先生关怀青少年成长,问某个孩子的,然后向他推荐了《表》这样的道德教育类少儿读物;另一处是贾宝玉问林黛玉,或者反过来,或者是薛宝钗问林黛玉,又或者是贾政问贾宝玉,也许是关心,也许是调情,也许是暗藏机锋,《红楼梦》里这种比生活还复杂的对话,我总也不敢说看懂了。反正有此一问。

最近在douban上看到某位地下书店店员的回忆,写得颇有大家风范,我深以为这是文艺工作者在体验生活之后交的报告。此地下书店中的地下,不是隐藏非常深而不得见的意思,而是真的就在地底下,可能是地铁,也可能是防空洞,我没注意。作者提到很多有意思的小细节,比如证明读者们都如何无知而装作有见识,讲到读者寻求推荐的时候问"最近得诺贝尔奖那个,他的书有没?",似乎并不知道"莫言"。生活在观察之中,作为店员生活得颇有情趣。他提到两件事很有趣,一个是午休的时候挤出时间看溜冰,溜冰的人像水里游泳的鱼,另一个是提到很多明星去买书,他们都买实用型的
(例子可能是,如何治疗拖延症?),只有周迅是个例外,她只买世界名著。

有人可能撇嘴了,"她们都是装的。"一则,人不能处处假装,比如在素不相识的书店店员面前,周迅如何得知这是位文学大家在卧底;二则,又如果周迅只钟看看微博传八卦,喜欢不超过三百字的贴子,但是她只买世界名著,这又不是她所钟爱的,那么她的生活得多么得痛苦啊。

有同学可能说,看看微博怎么了?看微博当然没什么,不过这语气总归有点像李某某的律师的语气,所有回答都是"这是俺们的合法权利"。看微博,就像有人喜欢吃方便面,喜欢吃方便粉丝,喜欢吃咸菜,总也不吃还非常想得慌。你可能会说,微博就方便面,世界名著就满汉全席。没错,我非常明确地认为,作品确实是分三六九等的,无论是思想境界,抽象的高度,还是什么。微博这样短小的文字,甚至包括博客这种篇幅,要是能让一个人进步,或者如某些人回忆到的看了读者知音故事会心灵鸡汤以后提着喷壶灌顶一般顿悟,那都怪了。

我们读那些短小的东西,只是为我们的观点、既有的知识结构找些佐证。就像小女孩左看看右看看,啊,原来大家都跟我一样胖,恩,放心了。这种感觉。颠覆我们观点的,让我们从另一个角度看自己,甚至否定自己的--这就是所谓成长--只能是足够厚的作品。或者偶尔,会有非常薄的作品起到这样的作用,比如离散数学,比如纯粹理性批判,比如爱因斯坦讲相对论的原始那张贴子,不过,无一例外,这种短贴子,非常之难看懂,你得花比看大部头还长的时间去读。

读短东西,我们不过是在自言自语,连反刍都算不上。

我固执地认为,程序员仍然是先进生产力的代表,一个重要原因是,他们在工作以后仍然保持读书。他们终日阅读,读的东西还都够长。

也许他们读的文字只是库函数手册,又或者需求书,但是他们在与别人进行相当精确和深入的交流。有些人也读了,但是只流于表面,用于证明关系,"啊,我也这么想,你看我们的关系多么铁",或者"我完全同意你说的,但是",或者双方各说各话,互无交集。程序员要精确地找出他反对的部分,然后与作者争辩,或者做实验,或者吵得面红耳赤。因为如果双方的观点完全相同,就连交流的必要都没有。这和我们变态的现实生活如此不同,在现实世界,往往观点相同,我们就要一万次确认,观点不同,我们甚至不再说一句话。在现实世界,我们竟然往往只与自己对话,这多么孤独和变态。还是做个程序员更正常。

他们所阅读的,并非自己或自己的同类,那些完全赞同自己观点的人,所产出的东西。不是有些人说,某某类的作品,口戚,我从来不看。以此给自己贴个值钱的标签,供人给些好的品评。毛泽东同志在瑞金的时候,据说曾有人说,你用孙子兵法指导革命战争,如何如何。毛同志说,你知道孙子兵法里面讲些什么,你知道孙子兵法一共有几章?不是有人只读我们自己的传统,不是有人只读非我们自己的传统的东西么。

同时,他们所阅读的,并非完全为了追求阅读当时的的和短暂的快乐,像有些文艺青年那样。他们的阅读是漫长而痛苦的,他们阅读的原因之一是为了追求阅读之后达成的共识,或者挖掘出的差异。这些共识和差异,可能是人与人的,也可能是我们的假想与真实世界之间的。所以,他们不只挑那些读起来爽的,听起来的好的。

这让我想起以前提到过的,小资们艳羡,李安有段时期由老婆供养,成天就是看片儿。看片儿之于李安,与小资们想像的是不同的,我相信,他一定不会只挑自己喜欢的看,还喝着汽水嗑着瓜子,他一定是把那些烦得不行的片子也要看了,看到吐,他一定是把自己喜欢得不行的片子也看了,品评分析很多遍,直到把骨头和肉和神经完全分开了,再也不是最初感情上肤浅地喜欢的那种。

程序员就是这样阅读的。而且,他们一直保持。

还有一些人,从成年 (成熟?腐烂?)开始,就再也不阅读,再也不阅读自己不"喜欢"的,再也不阅读长的,再也不阅读自己不认同或者不认同自己的,再也不阅读对业务
(比如宫斗或者宫斗)有用的任何书籍。他们少年时,读得最多的是考试辅导材料。他们成年时,读得最多的是他们的孩子的考试辅导材料。有些人,即使在工业社会,即使在后工业时代,仍然保持着光荣的传统的小农意识,除了圣经或者语录基本没有读过别的,除了家乡
(不管出生在多么大的城市)再没见过更广阔的土地。如此纯洁,不沾纤尘。

你最近阅读的,不是为了直接的短期见效的功利的动机,超过500页的作品,是什
么?

-----

附记:

今天读CSAPP,第9章。有一处卡住了一个小时,复核书上计算的结果怎么也不对。后来拿着书睡着了。拿着书睡着这件事,一点也不浪漫,它让我意识到衰老,也让我意识到没有读到什么好玩的东西,时光虚度。

CSAPP英文版第2版,826页,或者中文版第2版546页,有这么一段。关于多级页表的。

"But if we had a 32-bit address space, 4KB pages, and a 4-byte
PTE[page table entry, 杨注], then we would need a 4MB page table
resident in memory at all time..."

就这么一段,成了拦路虎。我跳过它,读了后面大半小节,没什么障碍。我是这
么翻译和理解的:

但是如果我们有一个32位的地址空间,每页长度4K字节,PTE每个为4字节,那么,我们将需要4M字节的页表一直驻留在内存中...

以上翻译,有几处需要解释。1.K是1024,不是1000,M是1024*1024,不是1000*1000。2. 4KB
pages,第一篇时我读错了,以为是4*1024页,又重读和读后面,发现作者习惯于这样措辞,其实是每页4KB这么长,即 "splitted
into pages of 4KB page size"。

然后,我按上述理解算了一个小时,没算明白。对不上。

其实这个公式很简单,我甚至google了一下以确认

页表尺寸 = (地址空间 / 页尺寸) * PTE入口大小........公式1

你按我上面的翻译手算一下左边和右边就知道了,不等。为什么不等呢?因为4KB中的B,还有4MB中的B,根本就不是"字节" byte,而是"比特" bit。

网络 (和中文?)中,习惯用 B 表示 字节,b表示 比特。但是CSAPP这段并没认可这个约定。

32-bit address space: 2^32 bit
4KB pages: 4K bytes
a 4-byte: 4 bytes



K = 2^10
M = 2^22

代入公式1的右侧,得

(2^32 bits / 4K bytes) * 4 bytes
= 2^32 * 2^2 / (2^2 * 2^10) bits
= 2^22 bits
= 2^2 M bits
= 4Mb

CSAPP原文和翻译均写作: 4MB

我是不是哪儿算错了啊,各位大师看出来的烦请告诉我。

--------------------

博客会手工同步到以下地址:

[http://giftdotyoung.blogspot.com]

[http://blog.csdn.net/younggift]

20130718

除了管道和重定向,还有命令行参数

除了管道和重定向,还有命令行参数

管道和重定向,在Unix Shell
Script入门教程里都要提到。在稍微深入一点的dos/windows批处理教程里也要提到。管道,一般在用前一个进程产生的输出,作为后一个进程的输入;重定向,一般用在把输出由控制台转到文件,或者用文件替代控制台输入。管道和重定向满足了进程的输入和输出的转向。

输入和输出,似乎本来就是进程跑起来以后唯一与外界的联系。根据与外界的联系判定那是个什么东西,而不是根据它的内心,这据说是萨特的观点,小资们不可不知。

不过,输入和输出,并非进程与外界唯一的联系。除了输入以外,命令行参数,也是设定进程行为的重要依据。在这一点上,如果把进程当成一个对象,命令行参数有点像构造函数的参数,虽然跑起来以后就跟它无关了。而且,unix程序一般地,只有命令行参数才是命令,影响进程的行为,而以后从标准输入读进来的所有东西,都只是数据而已,是被进程处理的东西,对进程本身没有影响。

shell (像python一样),是一种不错的粘合剂,它能组装一系的进程,让它们协同工作。管道和重定向,能让它们完成生产者-消费者这样的组合。

不过,如果我们需要一个进程的输出,不是作为另一个进程的输入,而是作为它的命令行参数呢?

下面的写法,
a | b
完成的,是把a的输出作为b的输入。

而如果我们希望下面这样,如何表达?
b -para <此处是a的输出>

有不止一种方法。

1. backtick

backtick,就是"`"。这不是单引号,而是键盘左上角,波浪线 (~)下面的那个小点,像个反向的单引号。

它的作用是,把括起来的命令执行了,并用执行结果中的每一行去替换命令所在的位置。

ls `echo \-l`

相当于

ls -l

我们把其中的一段拿出来单独执行一下。

1 $ echo \-l
2 -l

可见,"echo \-l"的结果,就是"-l"。其中的"\"中为了避免echo把"-l"当成自己
的参数。

而"-l"替换了"ls `echo \-l`"中反引号括起来的部分,所以"ls `echo \-l`"就
变成了"ls -l"。

再举个例子。

1 $ grep -w main `find . *c`
2 ./samples/hidraw/hid-example.c:int main(int argc, char **argv)
3 ./samples/trace_events/Makefile:# have that tracer file in its main
search path. This is because
4 ./Makefile:# $(vmlinux-main). Most are built-in.o files from
top-level directories
5 ./Makefile:# +--< $(vmlinux-main)
6 ./Makefile:vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
7 ./Makefile:vmlinux-all := $(vmlinux-init) $(vmlinux-main)
8 ...

这条命令的作用是,先执行 "find . *c",找到当前目录下所有子目录中的 c文件,然后对每个符合条件的文件执行 "grep -w main"。

命令"grep -w main `find . *c`"中,`find . *c`部分将被替换为很多个匹配的文件名,然后每个文件名都作为
"grep -w main"的命令行参数,类似于:

grep -w main a.c
grep -w main b.c
grep -w main c.c

这样。

2. $(cmd)

backtick倒是挺方便,不过很多同学反映,"`"这个符号太丑陋了。最丑陋的特性之一就是,它跟单引号"'"长得太过地相似了。像我这样认人脸有困难的,本来就希望大家服饰发型更多样化一些,所以你能够理解我现在看到女演员们长得都越来越像,该是多么大的困惑。我指的不光是韩国的,小锥子脸大眼睛面孔白而无表情。backtick也存在这样的问题,跟别人像,本来就是毛病。

所以有别的写法,比如这样:

grep -w main $(find . *c)

这样的效果跟backtick是一样的。一样一样的。区别呢?多少也有一些。比如 $(cmd) 这样写法是可以嵌套的。

3. xargs

不过上述这些需要"代换"的,似乎有些程序员理解起来有困难?后来出现了一个命令,xargs,专门用来做这件事。

find . *c | xargs grep -w main {}

请注意到find和grep的顺序换了。find的输出结果,由xargs转给grep作为命令行参数。事实上,是xargs调用了grep,管道的末端不是grep,而是xargs。这不同于管道的末端是grep,如果是这样,就成了find的输出作为grep的输入,是管道应用的更一般的情况。args把管道的输出转向了grep的命令行参数,而不是作为grep的输入。

"{}"在这里,将被find的输出替代。如果命令行参数的位置不重要,或者在最末端,那么还可以把"{}"省略掉,效果是一样的。像这样:

find . *c | xargs grep -w main

4. -exec

似乎有了xargs以后,还是有些程序员觉得不够简单?又有些命令自带了"-exec"参数,意谓:如果匹配了,就把那些输出作为"-exec后面的进程的命令行参数"。话说,有些事情由于它的领域模型就那么复杂,是不太可能因为表达方式而变得简单的。

以下命令,效果是一样的:
find . *c -exec grep -w main {} -nH \;

行尾的"\;",是为了标识"-exec"部分结束,其中的"\"是因为";"是个特殊字符。grep需要"-nH"参数,是因为不然输出的匹配行里没有文件名和行号。grep在此前的几个命令里用的是别名,这里是真正的grep本身。

5. 总结

以下几个命令等价:
$ cat `which 20.sh`
$ cat ${which 20.sh}
$ which 20.sh | xargs cat

cat不支持"-exec"参数,那是find这样的复杂命令的特色,因此以上总结中未包括。

----------

本文的命令在 emacs eshell 不好使,因为 eshell 对管道支持不充分,shell-mode则没有问题。

--------------------

博客会手工同步到以下地址:

----
杨贵福
东北师范大学 计算机科学与信息技术学院
--
Sincerely,
YANG Guifu
School of Computer Science and Information Technology
Northeast Normal University
Changchun, P.R.China

重剑无锋,大巧不工。

无不大工。