20131222
冬天的温泉
20131214
内战记
20131130
bug的定位比修改重要1000倍
20131116
挺简单的?那你就自己整
20131030
在Lenovo的Y430上安装Ubuntu,用nomodeset解决soft lockup
又临时整了一台笔记本,是联想(Lenovo)的Y430,Ideapad系列,预装了Vista。我拿到手的时候里面不知道装了windows的哪个版本,迟疑了一下要不要安装双系统,然后决定装Linux覆盖原来的windows。
还犹豫了一下要不要装Debian或者Arch。Debian,我没有马上找到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;在右下角的菜单里用回车选择nomodeset;ESC退回;选择"Installing Ubuntu还是玩意"。安装完成以后,安装系统会要求重新启动,把U盘拔了,重启。
20131026
图书防盗磁条原理
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
铁子同学的太原科幻之旅,我检查嗓子的科幻之旅
20131006
特别特别繁忙的九月
特别特别繁忙的九月
(照片有些好的,还没时间整理。)
我曾经计划过,在CSDN上每个月都发4篇以上的博客,这样能始终挂着"持之以恒"勋章。九月,我只发了两篇,前一篇是刚刚开始忙碌,后一篇是发现忙碌貌似永无绝期,月底将至,尽力而为写的。这期间,我甚至都没有时间去考虑"啥时候能忙到头"这件事。
以前有人说过,过一阵就好了,或者,这只是开头,以后就好了。我当时表达了反对,明天总是跟今天差不多,如果不是更坏的话。估计被鄙视了吧,不过事情却正是这样,王子与公主从此过上幸福的日子这种事,只有童话里才有,而且还得是在童话结束的时候。
九月之前,我保持每天读书4个小时,分门别类,各自有进度。就像大河上木筏子里的人,满怀希望地看着前方的地平线,两岸如画,柳风清扬,阳光和暖,心里盘算着,恩,还是多少公里折合多少天,我们就到入海口啦。然后就是港口和海鸥,大碗喝酒大块吃肉。可是,谁又知道下一刻暴风骤雨突然降临,河床塌陷漩涡急转,瀑布之声如雷贯耳就在左近。刚进入9月,我的整个读书进度几乎完全停滞。
你永远也想不到明天是个什么样子。当然,能看到明天,就是幸运了。就像一场游戏,你在战场上奋勇拼杀,而偶然因素像丛林里的野狼,在你左右窥伺。满以为发现了规律,比如金甲虫喷一会远程炮弹就仓库空虚,可以派小狗凑上前去群殴猛咬,你终于能放开手脚大干一场。突然,游戏的对方(可能是电脑)突然宣布,它输了;或者,敌人的空军从天而降,像一场大风,然后战场上你的兵全没了。无论是被敌人灭掉,还是敌人撒手不打了,都令人同样郁闷。
而我,只有呆坐在电脑前面。整个九月,我大部分时间就在电脑前傻坐着,屏幕上开着的主要是WORD。而大部分时间,我什么也没有键入,只是在想。或者,我站在白板前,半天也不画上一笔,我在等,等各个元素和它们的关系如水退潮,逐渐从迷雾里显现出来。什么时候,能不能,我也说不好,很没有把握。
罗素这样讨论过牛顿在太阳系各行星运行规律中的价值。他说:如果太阳系的流星们更大一些,牛顿定律就没多大价值了。因为更大的流星会经常把行星击打得四处翻滚流窜,虽然牛顿定律仍然是对的,甚至还可以根据它计算出流星与行星相撞以后各自的轨迹,但是这对于预测太阳东升月亮西落的时机难有帮助,更不用说对日食月食火星轨道的解释了。如果偶然因素在这个世界上占据了主要地位,那么,我们也不用再奢望预测未来了,连形成现在的那些规律也难以发现和了解。可是,如果未来一切注定,今天我们就能看到二十年后,那人生还值得去过么?
你可以想到,人生也许应该是那样吧,大河缓慢流淌,偶有微澜,又不至于落水。可是如果这一切都按你安排地去演,那和过家家又有什么区别。
九月,异常繁忙,没有时间写博客,也没有时间想更多。几乎,除了工作,就是倒头便睡。不过,我还是看完了CSAPP,从2012年11月至2013年10月;西方哲学史的前苏格拉底时代快要看完了。
九月过后,然后就是十月了。
20130929
去大连
20130911
以前能跑步的时候
20130830
对C语言的写文件操作fwrite的一个初学者常见误解
20130829
用windows sdk写一个贪吃蛇
20130828
今天暑假我做了什么
20130818
同学聚会
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
作者: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英文版第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上的一个疑问
"你最近在读什么书?"这句话我在两处看到过。一处是鲁迅先生关怀青少年成长,问某个孩子的,然后向他推荐了《表》这样的道德教育类少儿读物;另一处是贾宝玉问林黛玉,或者反过来,或者是薛宝钗问林黛玉,又或者是贾政问贾宝玉,也许是关心,也许是调情,也许是暗藏机锋,《红楼梦》里这种比生活还复杂的对话,我总也不敢说看懂了。反正有此一问。
最近在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
重剑无锋,大巧不工。
无不大工。