« October 2006 | Main | December 2006 »

November 30, 2006

Lua Debugger

luadbg.png

最近想做一个 visual 版本的 Lua 远程调试器。奋战了两天弄出这样一个玩意出来。如果有精力做完,就可以在 Windows 下远程调试任何地方的 Lua 代码了 :D

如果近期没精力,就开源让别人继续做好了。

November 24, 2006

lua 代码的断点调试

Lua 5.1 带了一个 debug 库,把所有的 C API 中的 debug 相关 api 都导出了。作为独立的语言使用的话,这些足够搭建一套方便的调试库。

说到最常用的断点调试法,我们能想到的最直接的方法就是利用 lua debug 库中的 hook ,然后记录一张断点位置表,设置行模式的 hook ,每次进入 hook 都检查是否是断点处,若是就停下来等待交互调试。

这个方法很有效,但是很消耗 cpu 。因为每进入一个新的代码行,都需要回调一个函数。当这个函数本身又是用 lua 写的时候,效率更低。

本文提供另一种思路,换一个方法设置断点,让没有断点时不影响运行效率。

简单的说就是在代码中插入硬断点。

C 语言调试时,很多调试器其实是动态的在进程中的程序码中插入 int 3 ,让程序执行到断点处可以触发调试中断,恢复运行时只需要把 int 3 抹去换上原本的机器码即可。

lua 虚拟机并没有提供调试中断指令,向 byte code 中插入代码也没有合适的途径。但是不要忘了,动态语言最大的好处之一就是解释执行,不需要编译链接。所以,我们把调试中断的调用硬编码到代码中也不是难事。调试之后去掉这些多余的代码即可。只要有硬编码进的调试断点,我们再为这些断点写动态的开关就是很简单的事情了。

今天我实现了这么一套简单的调试库,完全用 lua 本身实现的,只需要用 require "bp" 就可以加载进来使用。源代码我贴在了 wiki 上

下面讲讲简单的思路:

我把断点分成两类,一类是匿名断点,一类是具名断点。分别用 bp.bp() 和 bp.bp "name" 来设置。 匿名断点在第一次设置时初始化,并激活。也就是说,程序运行到 bp.bp() 处就会断下来。系统会为这个断点分配一个唯一 id 号。以后可以通过 id 来激活和关闭这个断点。方法是 bp.trigger(id,true/false) 默认 bp.trigger(id) 是激活断点。

这样的断点内部是用 closure 的对象地址做标识的,也就是说,同一个 function 的不同实例可以有相互独立的断点 id 。这样比靠源代码定位的方式更加灵活。closure 在动态语言中的使用非常广泛,相同一份代码可能在不同状态下干着不同的事情,我们可以为特定的 closure 设中断了。

当 closure 垃圾回收后,断点也会被回收。

具名断点适合对一批断点同时开关,因为有字符串名字,所以可以在断点运行到之前就设置好状态。有更高的可控性。

在云风的这个系统中,可以用 bp.list "name" 列出所有命名为 "name" 的断点所在代码位置,也就是说,可以给无限个断点起相同的名字,一致的控制。

bp.list() 可以列出系统中已有的所有断点。

为了配合这套断点调试系统,我附带做了几个小东西。一个就是递归打印 table ,函数名为 bp.print_r(tbl,limit=64)

由于 lua 的 table 有可能循环引用,或是元素太多,我在这里设置上输出上限,默认是 64 。超过上限的以 ... 结束。

另外,这里给 debug.debug() 这个调试控制台增加了方便的 local 变量以及 upvalue 的控制方法。原有的 debug.getlocal 和 debug.getupvalue 实在难用了点。

现在进入控制台后,可以通过 _L 和 _U 两张表,分别控制 local 和 upvalue 了。

可以在控制台试试这样一个例子:

require "bp"

function foo(arg) bp.bp() -- 设置一个匿名断点 return arg end

=foo(0) -- 输出 foo(0)

运行后我们发现立刻进入了调试环境,输出:

break point:    1       on      =stdin(2)
_L=     {arg=0,}
_U=     {}

表示 1 号断点中断,源码行是 stdin 的第 2 行。局部变量有 arg=0 一个,upvalue 没有。 现在可以输入 _L.arg=1 ,然后输入 cont 继续运行程序。你会发现屏幕输出是 1 而不是 0 。arg 变量已经被修改了。


大体上就是这样了,周末如果有空,我会把单步运行加上,基本就可以及的上简单的 gdb 功能了 :D

November 17, 2006

Lua 中写 C 扩展库时用到的一些技巧

今天方舟组的同事问到我一些 lua 的问题,主要是关于 C 扩展库的。我觉得有些技巧性的东西挺值得跟大家分享一下,那么就写篇 blog 吧。

通常,C 扩展库中 C 代码会有一些数据要放在 lua 状态机中。Lua 提供的方案是放在它的 注册表 中。如文档所言,因为 Lua 的注册表是全局共享的,选择 key 的时候就要千万小心了。整数 key 已经被 reference 系统用掉了,一般我们会采用字符串作 key 。

从 C 中压入字符串的效率不是最高,这是因为外部字符串进入状态机时需要重新 hash 并检查唯一性所致。关于避免直接压入字符串的问题,以前写过一篇 blog 谈过。( btw, 以前那个方法也不是最好的解决方案,不过还是可以作为一个参考的 :)

很容易想到,最方便且能保证唯一性的 key 是一个 light userdata 。这一点,在参考手册以及 Programming in Lua 中都有提到。

我们可以用一个 static 变量的地址作为 key 在注册表做索引保存需要的值。比如如下代码:

static const void *key = 0; lua_pushlightuserdata(L, (void *)&key); lua_pushnumber(L, myNumber); lua_settable(L, LUA_REGISTRYINDEX);

这样就把一个 C 中的数字 myNumber 放在注册表中了。如果以后要读出来当然可以用这样的代码:

lua_pushlightuserdata(L, (void *)&key); lua_gettable(L, LUA_REGISTRYINDEX); myNumber = lua_tonumber(L, -1);

这里用的是一个 static 变量的地址作 key ,所以绝对不会和被的扩展库冲突。但是这样做有一个缺点,就是 key 这个东西以一个全局变量形式出现,不太美观。而且,当我们想用到这个 key 的时候,不是那么容易拿到。要么,你得 extern 出这个 key ;要么你需要以一个单件的形式暴露一个函数拿到这个 key ,效率上或许又打了一些折扣了。

现在就给出一个折中的方案,或许能让绝大多数人满意: 那就是,以一个 light userdata 做 key ,这个 key 本身再和一个唯一字符串关联起来。每次再需要拿到 key 指针的函数中这些写:

static const void *key = 0; if (key==0) { lua_getfield (L, LUA_REGISTRYINDEX, "MyExtensionLibrary"); key=lua_touserdata(L,-1); lua_pop(L,1); if (key==0) { key=(void*)&key; lua_pushlightuserdata(L,key); lua_setfield(L, LUA_REGISTRYINDEX, "MyExtensionLibrary"); } }

这样,我们就可以对 key 做一次惰性初始化了。以上只是示例,初始化部分可以做成一个函数和宏方便重复调用。


顺便再提一个容易被忽略的小技巧: 我们通常用 full userdata 来实现复杂的 C 结构,或是 C++ 中的对象,把它们用于 Lua 中。经常我们需要把 Lua 中的一堆数据关联在这个 userdata 上。为了让 Lua 的 gc 过程可以正确的工作,我们需要做一些额外的工作。

最下策的解决方案是,在 userdata 中保存一个相关的 Lua 表的 reference ,然后给 userdata 加上一个元表,实现一个 gc 的元方法。在 gc 事件发生时,unref 掉这个 Lua 表。这个方案最糟糕的地方在于,实现过于繁琐。而且给 gc 过程带来许多额外的执行开销。

中策是在注册表中保留一个映射表,并把它设置为 weak table 。每次 userdata 创建出来,就以 userdata 为 key 在这个影射表中保存相关联的 Lua 数据。那么这样,垃圾回收的过程就可以由 Lua 本身自行维护了。其不方便之处在于,每次 C 函数想访问 userdata 相关的 Lua 数据时,需要先拿到注册表中的这个映射表(这个操作或许用的到这篇 blog 前半部分提到的东西)。

其实我们有一个上策,Lua 设计的时候就考虑到了。那就是 full userdata 可以拥有一个独立的环境表。这个东西对 Lua 本身毫无意义。但是却参与 gc 。我们只需要用 lua_getfenvlua_setfenv 来读写这个表就够了 :D

November 16, 2006

宗教与科学(转载)

这两天在公司内部收到游戏研究组的文章,其中大块的文字是谈科学的。就这个问题我们展开了一些讨论。这类话题在我们部门内部也经常展开,往往是从谈论伪科学起的。

其实大道理我自己也讲不太清,还是转载一篇感觉不错的文章:

宗教与科学

一、感觉、认识与真理

记得我读书的时候,有一次和同学讨论,谈到在场所有的人所看到的颜色在内心里所产生的印象是否是一致的。我当时认为这个问题是没法检验的。对于我自己来说,我的感觉是生动的,在我的意识层面上有生动的像。比如当我看到一个红苹果,它的样子是具体而生动的,而且是实在的。但是如果我们大家都在观察这个苹果,并且都认为我们看到的是一个红色的苹果,而且对它的描述在语义上是一致的,那么是否表示我们的意识所“看到”的东西也是一致的呢?我认为这个问题至少在目前的技术水平上是无法检验的。我们所有的人,只是由于采用了同样的通信协议,而且我们具有相同的人的属性,才能够就我们所看到的东西进行交流,这是第一。第二,认识的实践性意义说明,我们的感觉和我们的行动是适应的。感觉和行为的协调是从出生开始便一同发展(见皮亚杰),感觉和行动的协调最后导致我们得到了一个关于世界的构造,或者是关于这个世界的一个“正确”的像。并且所有的人由于我们作为人的属性的一致,在行动和通信水平上是一致的,所以我们的认识可以统一起来。只有与行动的协调的水平上,我们才能说我们有“正确”的认识。

但是如果我们对认识进一步拓展,问题就出来了。在人的整个成长时期,我们所建立的日常经验用于对付我们的生活是足够的了。但是对于日常经验以外的认识,我们必须通过大量的实验和假说来建立关于世界的构造。在这种意义上,应该说世界是自在自为的,不论你对于它有什么样的认识或者理论,它总是在那里。有人可能会拿出测不准原理来说明自然界本身会欺骗我们。但我要指出这乃是由于人的测量干预了自然进程,导致你对于自然的误解。不仅在日常经验以外的认识是如此,即便是日常生活中的问题,我们的感觉也常常给我们错误的印象,从而也影响到我们的认识。视觉错觉是最明显的例子。大量的实验说明视觉错觉不仅存在,而且几乎不能纠正。关于视觉发育的实验,有很多动物实验更说明了这一点。有人用猫做实验,在小猫刚刚出生的最初一个几个星期,把它饲养在一个只一些有很窄的竖条栅栏能够透光的笼舍里,使它的视网膜中对于横向线条敏感的细胞不能得到刺激,因而不能得到很好的发育。等到被放出来以后,这个可怜的猫咪对于世界的错误的感觉就永远的不到纠正了。关于这只猫,我们人类究竟有多少优越感?我只能说,在人类的进化过程中,我们的感觉器官仅仅进化到一个足够的水平,进一步可以推想我们的智力也仅仅进化到一个大致够用的水平。也就是说,关于这个自在自为的世界,我们只看到了我们需要看到的东西。有些动物的感光光谱范围要比我们偏向紫外,而另一些动物则根本看不见什么光,他们关于这个世界的观点肯定与我们不同,但我们生活在同一个世界里。可以设想某种外星人与我们关于这个世界的认识的大相径庭,但他们的认识与他们的活动象我们一样是相协调的。按照一般人的观点,科学能够达到真理,或者说是正确的认识。但是这种“正确”的认识仅仅在实践的水平是才是有意义的。也就是说,真理与我们的实践是相互协调一致的。首先就感觉而言,我们的感觉并不能忠实地对世界进行模写(以如前所述)。其次,科学探索活动的目的固然是为了追求真理,但探索的结果的“正确性”并不高出我们的常识。人类关于这个世界的所有的认识都具有人的性质。也就是说,人的各种知识是为人服务的。所谓真理也是在这一层意义上的真理。超乎“人”的绝对的真理,放之宇宙间一切生灵而“皆准”的绝对真理是不存在的,而且是没有意义、难于想象的(并不是说绝对真理不存在,而是说“这种”绝对真理不存在)。但这并不等于说真理是没有意义的。科学探索的活动是为了拓展人类的活动的时空范围。通过探索而达到的正确的知识可以实现这个目的,而错误的认识达不到这个目的。这就更说明了真理的实践性的意义。但这样说并不等同于经验主义。就实践的意义而言,经验主义的范围是狭窄的。经验知识只能满足一个非常具体的活动的需要。而科学探索的结果,也就是科学知识,是关于世界的构造的整体的认识。科学总是企图得到关于这个世界的整体的时空构造体系,只有这样才能满足人类达到真理的认识的需要。所以,所谓真理。反映了人类对于拓展无限的活动范围的企图。

当然,我并不是说科学理论绝对代表了当前人类活动的范围,它只是代表了活动范围的可能性。科学使人类在其活动和观察范围内能够对世界的进程作出预言,从而能够指导人类的活动。

二、科学

人的认识的“正确性”只是就人的活动能力和活动范围而言,科学理论也只是基于实验的逻辑推演。公理的正确性也只是就其实践性而言。一个依错误的公理而建立起来的理论体系在实践上是没有意义的。科学并不绝对保证在某个确定的阶段上其结果的正确性。科学的内涵包括科学精神、科学原则和通过科学活动而达到的知识。科学的精髓是科学精神。科学精神保证了科学能够不断地产生内在的动力向前发展,自我修正,并且与人类的活动能力和活动范围的发展相协调(见库恩)。系统地建立科学体系的企图始自亚里士多德。但是我们如果把他的科学著作拿来读一下,会发现里面充满了谬误。但那的确是最初的科学著作。科学概念从现实经验对象开始(客观性),而摈弃一切神秘主义的东西。神秘主义导致我们对一些基本概念不再追究,反正把它交给某个万能的大师就行了,不管我们的事。科学从人的实践和观察开始,并且建立关于某个分类学科的体系。科学要求提供假设和解释,并依据正确的逻辑建立体系。亚里士多德的科学尽管满纸荒唐言,但体现了这样一个过程。在他的时代,简陋的实验和观察手段,知识的浅陋,人类活动能力的有限,使得他不可能达到今天的成果。科学活动是一个建构的过程。建构是一个主动的过程,所谓绝对的客观的观察是没有意义的。我们的头脑并不是一个照相机,一架照相机也不能理解它的感光底版上的像的意义。人类的认识依据一个认识框架,我们的意识所“看到”的东西是经过了一个认识框架,并在意识环境里得到定位的像。甚至人的感觉,也不是绝对客观的。感觉细胞已经包含了初步的模式化工具,所以感觉已经有初步的加工在里面了。更不用说,认识到的对象取决于我们的认识模式。而这里面就已经有可能有谬误在里面了。当然,只要与我们的实践活动或者理论体系不相孛,那也就无所谓,也发现不了问题所在。作为建构的科学,人类的活动起了很大的作用。假设的提出就必须依照我们头脑中现有的世界体系,然后我们设法把假设与现有的体系和实践活动相协调一致。如果做不到,就要从头再来。目的是要提出学说,学说要具有“合理性”。什么是合理性呢?这就是需要在实践的意义上进行协调,没有条件进行实验的,只能在常识或者现有的理论体系上进行协调。现有的体系则是前人的科学工作的结果。前面已经说了我们的知识,或者说我们头脑中关于这个世界的构造体系,是人与世界相互作用的结果,它是认识主体与世界之间的一个中介。它既不是客观世界本身,也不是认识主体,但它在主体一边,是主体的附属物。它的形成包含了体系的建构过程和与实践相协调的过程。因此,客观性仍然体现在实践性上。实践能力的不断扩展,观察手段的不断增强,知识的不断增广和加深,使人类的知识不断地由零散走向系统化,不断地纠正自己的认识。科学总是要不断地把事实包容到自己的理论体系中来。科学不可能排斥即已存在的现象和事实。这也是纯粹的理性主义与科学的区别。理性主义企图以理性框架来规范世界,并且理性主义总想一次就把事情搞完,什么都是完备的了,整个宇宙都已经包容在理性框架里面了。而且理性主义有一个特点,就是免不了有价值标记在他们的理论成果里面,理性主义总企图以和谐作为构造世界的最终目标。当然他们没有必要这样做。理性主义者热衷于构造一个完美的体系,并且似乎总包含着最终的价值目标,这就使得理性主义似乎总与宗教纠缠不清。理性主义的世界是静止的,秩序井然的,即便是存在发展和运动也是符合美学的。科学则从来不以人类的一相情愿来规范世界。老黑格尔的哲学的发展观算是够浪漫的了,结果还是脱离不了发展的最终目标这种“大团圆”式的结局。理性主义者总是想当总设计师,连马克思这个黑格尔的学生都免不了中这个毒。科学总是在前进的。它从事实出发,总是在不断地打破自己的结构,不断地重建体系,不断地与人类的实践相互促进。尽管“在科学理论的范围内,存在着包容整个世界、追求类似于形而上学目的的种种尝试,所不同的是,科学对严酷的、顽强的事实负有更大的责任”(罗素)。然而科学不仅仅强调要包容即有的事实。科学理论首先是一个理论体系,这就和经验主义和实证主义不一样。实证主义者不敢超越经验和经验描述的范围内,而科学体系本身并不与康德的形而上学相孛。宗教理论体系同样地既企图包容所有的事实,也追求内部的逻辑一致(由于神秘主义甚至连这个都不要,我确实不知道他们用来传道的语言如何实施。当然,他们不追求这种逻辑上的一致性,所以这个问题对于他们是没有意义的,他们完全可以不理会这种发难)。但科学与宗教和神秘主义相区别的最重要的一点在于,科学一般地与价值无关。科学中关于世界的基本概念始自现实经验对象,因此科学要求有一个基本的信念,就是自在自为的客观世界。科学的任务是要建立关于这个世界的知识系统,这就要求不能以价值判断来代替分析。而恰恰在基本信念这一点上,科学又与宗教纠缠不清。在这里我只能说,宗教的基本信念是一个关于价值的信仰,这种价值信仰容易带来认识上偏离客观性。

三、唯物主义的科学信念

写到这里,好象出了一点问题。“自在自为的客观世界”与其说是科学的基本信念,不如说是唯物主义的信念。唯物主义是什么?首先它肯定不是一个认识过程中的结论,它应该是一种基本的认识立场、态度。换言之,这种基本的态度和立场似乎是没有实践性意义的。如果不这么看,谁难道能把唯物主义的当作一个认识结论?我们可以做一下这种尝试。

假设我们搞它一个最朴素的认识模型(其实这个模型本身就已经有了一个预设在里面了)。所谓最朴素的,是想说明这个意思,它没有更多的知识在里面,没有什么先入为主的观点在里面,只有最原始的观察经验。这个模型包括三个部分,一个世界W(世界的存在谁也否定不了),一个认识主体S(没有办法,这是一个朴素的经验,我扔不掉它),一个世界和主体之间的中介M(总得搭一个桥啊)。把主体和世界对立起来可能不是一个能让大家公认的办法,但是我们可以在后面的分析中来设法弥合他们。

W<====>M<====>S

唯物主义肯定W的真实性,进而也就肯定了M和S的真实性。假设我们认为现在还不能把W的真实性作为一个一元概念,必须经过分析才能证实。头脑朴素的人可能会以M来肯定W的真实性,但这和以W或S来肯定其他部件没有什么区别,让他们互相证实显然等于什么也没有说。现在再走得稍微远一点。S到底是什么?它是否是一个可以科学地确认的东西?“我”作为认识主体的一部分,每时每刻都在经验着世界和自我意识的活动。一个纯粹的“我”如果是“自在自为”的,它究竟有什么含义?如果我的活动能给“我”每时每刻的体验,那么“我”就是有一定功能的。一个纯粹的“我”怎样才能承担起这么多的活动呢?把我的“活动”与世界的活动对立起来可能也没有什么意义。如果我们把S的认识活动当作一个系统的功能(糟糕,又回到老路上去了,承认了主体的认识功能依赖于一个客观实体),那么“自我意识”是否也是这个系统的功能的一部分呢?再这么搞下去,就只剩下了一个纯粹的“自我”E(Ego),就有了下面的模型。|

W<====>M<====>S<===|===>E |

结果与Ego相对立的就是一个“大世界”(W+M+S)了。那么我们好象可以说,大世界的存在是E的存在的依据。但对于个人的经验来说,大世界的存在又依赖于E的存在。但如果我们以E为第一性,那么干脆连历史也否定算了,因为“我”一死,也就无所谓什么“大世界”了。以上的分析也许没有什么意义。反正我们的基本信念对于科学的实践性来说,没有什么问题,地球照样转,日子照样过,别人死了我还没死,我死了还有我的子子孙孙。上面讨论的实际上是唯物主义一元论世界观的问题。我前面所说的“好象出了点问题”的地方就在这里。如果说,科学的精髓在于对于事实的关注、在于对现实世界的不断地探索,那么说自在自为的客观世界是科学的基本信念就似乎不那么合适了。文艺复兴以来,很多可以既称得上是科学家,又可以称得上是哲学家的大师们,同样也在对上面讨论的问题进行探索。由于这些人的形而上学的倾向十分突出,他们必须要解决整个体系构造中最基本的问题。十七世纪的哲学家(算不算是科学家?)笛卡儿对于上面的“大W”和E到底谁支持谁的问题感觉到解决起来很棘手,干脆搞了一个二元论的折中方案,这个方案必须把上帝引近来(在我们看来的确挺可笑的)。他弄了个所谓的“两个钟”,一个管大W,一个管E,都走的特准,正好配在一起。这个理论后来被他的学生发展成了“偶因论”。

四、科学与信仰

存在着以下几种观点:

1.科学也是一种信仰;

2.科学与宗教信仰并不相孛;3.存在科学迷信。

确实存在着一些现象支持这些观点。但我们如果不加分析地接收这些观点,就会带来思想的混乱。关于第一点,我在前面已经说过,科学也有自己的基本信念。但这种信念与价值无关,这就与宗教不一样。说科学具有基本的信念和立场,并不能等同于说科学也是一信仰。第二,在思想上,科学与宗教有些纠缠的地方,我认为是由于:第一,当今基督教、佛教宣称并不排斥科学,甚至“李老师”的“法轮教”也使用大量的科学术语和概念;第二,很多大科学家似乎接受上帝这个概念;第三,有些科学家本身信仰宗教,甚至当今我国的一些“高级知识分子”也信仰“李老师”的“法轮教”。对于第一点,我想是由于两方面的原因造成的。首先科学的步伐始终是雄健的,科学进展所向披靡,谁也不敢否认。于是宗教如果要继续发展,就必须对自身进调整,使自己与科学相容。其次,宗教,特别是基督教,由于中世纪经院哲学的原因,总是企图以理性的方式达到关于世界的整体构造;也就是说,过于强调价值而不考虑现实世界的过程,宗教自身是难以为继的。尽管宗教并不以此为己任,但它做不到价值与真理的完全脱离。关于第二点,当科学家在他们的科学理论里大谈上帝的时候,实际上是在讨论他们的形而上学系统,因为这样一个世界在他们手中要成为一个完整的构造,必须引入一个上帝的概念才能完成这个任务。但是他们并没有讨论价值问题。不过上帝谈多了,难免会引出宗教情结。关于第三点,我个人的看法是,在一些西方科学家那里,他们的头脑中的确是科学与价值相分离的,既存在宗教的世界世界观,又存在一个科学体系。在我们这个世界上,每个人都要解决价值观的问题,而价值观又受文化的影响,所以宗教影响到科学家的信仰是不足为怪的。再者,就科学与宗教所关心的对象而言,两者似乎是不应该在一个尺度上形成对立的。这个问题下面在会进一步讨论。而我们的那些相信“法轮教”的“高级知识分子”们,以我看顶多算个受过科学方法训练的匠人。最后,应该说的确存在着科学迷信的现象。所谓科学迷信,是盲目地、不加批判地接受别人科学工作的结果,而没有领会科学精神。当今的应试教育容易培养出这样的科学迷信分子。由于科学发展到今天,容易给人一种印象,似乎所有的问题都有完满的解释,我们只须死记硬背这些知识成果就行了,而且必须背得不能错一个字,前人都是伟大的科学家,我们只能望其项背。科学探索的工作是很高深的事情,你们不要做。所以我们的任务是学习科学知识,而科学本身的任务与我们无关。这种科学迷信的确是一种退化呀。

五、宗教

宗教的起源与科学是不一样的。宗教起源于对于人类的幸福的道路的探索,所以它关心的是价值。宗教要解决象“真善忍”一类的问题。宗教的创始人几乎无一例外地都具有挺了不得的人格感召力。耶酥是个大善人。释迦摩尼也是“心太软”。在我们四千万“法轮教”教徒的眼里,好象李老师也是一个道德高人。无论各门宗教理论体系相差十万八千里,这一点是不变的。

问题在于,如果宗教企图完成它的价值目标,就不能不对世界的发生发展提出一整套说法。比如基督教说上帝七天就干完了创造世界的活,佛教说世界以轮回方式运转,等等。这样一来,宗教理论里就有了一套关于世界的构造的东西了。特别是基督教,这方面表现得极其认真执着。应该公正地说,基督教在这方面的认真对于人类思想体系的发展是做出了贡献的。中世纪的经院哲学在形而上学方面发展出了极为精致细微的思想和语言逻辑,训练出了很多极富创造力的大思想家。他们的思辩过程的确是充满了智慧的。比较杰出的有象圣奥古斯丁、托马斯阿奎那、奥卡姆等人。这里的关键在于,中世纪的经院哲学家企图努力通过理性尽可能地彰明信仰。尽管同时存在对理性的反动,但是这种努力从来没有中断过。由于西方思想的发展是寄生于希腊文明的,希腊的理性精神就成了经院哲学发展的温床。更重要的是,由于理性传统的存在,每当宗教原则与理性发生冲突时,宗教就必须修正自己的立场,因为只有通过理性论证才能解决冲突。结果是宗教就不能随意胡说八道,而必须为理性让步(理性主义的批判精神的确厉害)。最后当然是皆大欢喜,哲学作为宗教的婢女获得了一席地位,而宗教则借理性更加昌行于世了。

基督教的这种与理性相协调的发展过程,一直持续到今天也没有间断过,而且我相信永远不会间断。不过,基督教的这种努力恐怕会给自己带来问题。当今世界的科学昌明是基督教不能否认的,所以眼下基督教协调的对象是科学。但是,科学是比理性主义来得更严酷的东西,理性主义可以不特别关注事实,而科学就不行。所以,宗教如果企图兼容全部科学,就有很大的困难。首先基督教必须坚持上帝创造世界的工程是一个奇迹,接着它又要承认世界的运转是符合科学的。结果怎么样?我手头有一本为孩子们写的基督教宣传小册子,它用大量的气象学、考古学、地质学和地理学知识论证奇迹的存在,为了支持奇迹的存在,它不仅要运用部分科学知识,还要否定部分科学知识,比如它说进化论是反科学的。在这本小书里,它问孩子们,你难道是低等动物猴子进化来的吗?这显然是用价值判断来代替科学分析了。佛教与基督教不同。首先它的发展过程中没有遇到那个讨厌的迂腐理性的干扰。再者它干脆就认为所谓思想的普遍原则是属于人的东西,是一种中介桥梁,到时候就要扔掉。如果到达了大智慧的彼岸,这个拐棍就用不着了。这就是所谓“得意忘形”的意思。形式只是一个中介。如果我们能够直接地“悟”到了彼岸,思想和知识之船就可以根本不用。所以中介也不是必须的(禅宗)。在这一点上,佛教的确有一些很深刻的东西。佛教认为世界是完全自在自为的,我们所看到的东西不过是我们的执着,都是假象。修炼的目的是为了达到大智慧(圆融)。由于知识语言等等都是属于一种所谓“差别智”,是一种低等的智慧,修炼完成以后,真正的大智慧是说不出来的。也没有必要与别人交流。也就是说,进入到了一个境界。所以,尽管佛教倡导要达到世界大同,佛教徒要以普渡众生为己任,但佛教思想对于人类的进步也有消极的一面。与基督教相比,佛教有一点我认为比较好,就是佛教基本上不认为存在什么至高无上的神,也不需要信仰上的代理人,佛教神职人员只是帮助你进门的师傅而已。每个人都可以通过自己的修行而成佛,修行是自己的事。这样,佛教本质上是不允许邪教的成分存在的。另一方面,佛教思想对于人的心性、人的悟性和洞见能力也有很好的训练。特别地,佛教思想容入了很多民族的文化当中,对于这些民族的文化的发展是有所贡献的。最后,与基督教一样,佛教也企图兼容科学。但这一点不如基督教做得好。当然佛教也有自己的解决办法。既然佛教认为科学知识(只能是科学知识,佛教可能排斥科学精神)只是低等智慧的产物,当出现不兼容的时候,完全可以不理会科学,“你算老几啊,和你说不清楚!”

六、神秘主义和邪教

按说神秘主义应该不完全等同于邪教,但第一我对神秘主义没有多少了解,单独写可能说不了什么东西。二者我认为邪教包含有神秘主义的成分,所以我把两个东西放在一起来讨论。首先神秘主义是一种纯粹的观念上的东西。神秘主义者相信一些希奇古怪的东西,把世界的运动归结为一些现实经验以外的作用因素,这些因素的存在不需要证明,你只能相信它。而且,信仰本身是不需要证明的,价值高于一切。这一点佛教也是一样的,修行首先是要有信仰,理性证明是迂腐的。一个教佛学的大学教授,尽管学富五车,如果他没有信仰也是白搭。反过来,一个目不识丁的小沙弥就不一样,因为人家“信”,就走上了成佛的道路。菩萨说,“只要有一念信,就有无量功德”。比佛教思想还要彻底,神秘主义根本否定在客观概念上建立起来的知识体系和思想的普遍原则。所以,神秘主义不仅对于人类的进步是有害的,而且,由于神秘主义的确存在着人类精神状态的普遍根源和社会根源,它对于社会安全有很大的危害性。邪教的思想体系的神秘主义的。更重要的是,邪教是有组织的。与佛教不同,邪教实质上坚持必须要有信仰上的代理人。不管你自称是“七老师”还是“八老师”,实际上你是把自己当作信仰的代理人,甚至救世主。尽管基督教的神职人员比佛教的神职人员在信仰上的地位高,但是自从宗教改革者呼吁每个人都可以自己与上帝发生关系以来,基督教对于社会安全的影响就小多了,腐败也减少了。邪教不一样,由于它的神秘主义思想基础,而且把信仰当作至高无上的东西,使得很多人在人生价值的问题上被感召,甚至包括很多受过科学训练的人。而且它没有理性主义之剑的监视,它排斥知识,排斥人类的正常的思考,扼杀教徒的批判能力,最后的结果是导致全体教徒的盲信和精神病大发作。

七、结论

以上讨论想阐述这样几个观点:1.认识过程是人与客观世界相互作用的过程。2.感觉、知觉、认识和知识并不能得到客观世界的真实写照,它只是我们和世界之间的一个作用中介。3.绝对的正确性是没有意义的,正确性来自实践性。4.科学要对事实负责,所以科学具有自身的发展动力。5.科学的确需要一个基本的信念,但这个信念不是宗教信仰,它与价值无关。

6.宗教与科学的调和很困难,而且由于宗教本身的问题,它不可能只管信仰问题而不理会世界的构造;也就是说,宗教里的一部分的确与科学在同一个尺度上相对立。所以结果是,科学并没有干涉宗教,倒是宗教想染指科学。7.邪教是有害的,必须坚决取缔。以上说法恳请大家斧正。

来源:水木清华站

November 13, 2006

Lua 5.1 中文手册

前段时间安排同事工作时间翻译了 Lua 的参考手册 。当时的目的是想让翻译的人可以借翻译的机会深入了解这门语言。另外,其他人在查手册的时候也可以轻松一点。

事与愿违,这个中译本陆续经过了几个月的翻译和校对后,可读性依然欠缺。往往我需要去手册里查点东西的时候,都发现还不如直接看英文原版。中文译文读的晦涩倒是次要的,主要是一到关键点上就译的含糊不清,甚至有错误。

这倒符合我的观点,翻译技术资料,首先要求的是对原文的理解,然后是中文的组织水平,最后才是英文水平。

周末,我突然发神经,自己动手译了一下(其实起先只是想看看翻译这个篇幅的文档大约需要多少工时)。花掉两个半天敲了大约一万两千汉字,手都快抽筋了:)把最重要的一部分关于 Lua 语言的译完。

我想我的英文水平是很糟糕的,中文能力也不怎么样,不过满足翻译技术文档的第一要点:对 Lua 本身是很熟悉的。所以这个译本当是能看吧。

Lua 5.1 中文参考手册

很多技术术语我没有按标准译法来译,尽量用一些更通俗但是更繁杂的译法。大部分译词,如果我认为大家普遍能接受,就统一用中文译词,只在第一次出现时括号注明英文原文。例如:事件 (event) 、元方法 (metamethod)。

如果觉得译词不太能被大众接受,大部分地方保留英文,而在第一次出现的地方括号注明我认为合适的中文译法。例如 metatable (元表)、closure(闭包)。


2006年11月19日 凌晨: 手册的第三部分(C API)业已完成 :D

November 05, 2006

三人成虎

周四的晚上,准确的说是周五凌晨。我被通知参加白天在广州的会议,乘早上第一班飞机。原本我还躺在床上计划周五的工作的,因为觉得这次会议更重要一些,就调好闹钟,把计划放在一边,睡了过去。五点起来赶去机场,下了飞机就直奔会场。会开到了周六的凌晨三点,吃完中饭就折返回来。精神和身体都很疲惫。

这次会议上偶然提到一件事情,跟会议的主题关系倒不太大。就是业内挺有名的“七七”事件。这件事情的热度远超我当初的想象,直接用 google 或 baidu 查 "七七事件" 就可以发现其排名居然在 70 年前那件事之上。

这件事情的起因在于所谓的玩家改名事件,梦幻西游中有一个玩家用了不雅的名字,“干死4小日本”。在被强制改名后引出的一系列争端。此中谁是谁非,这里无意评论。事实上,所谓七七事件之后,起源的那些事情已经不太被人提起,(帮派改名,太阳旗,狮子头等等)。最重要的一个谣言就是,梦幻西游被卖给了日本公司运营。当然还有别的一些版本,比如部分服务器交给日本公司运营。

这个谣言在接连的几个月中,直到最近一个星期,还有认识我的朋友问起。一开始,我认为这个谣言过于可笑。因为没有任何的依据,我们公司里面也没有任何的迹象可以引发这样的一个谣言。在我看来,这种谣言都有人相信,只能说是相信它的人太没有对事情的判断能力了。我这种想法似乎和公司大多数人相同,大家都不屑于做点什么解释。仿佛如果去解释什么,好象还真有点问题了。

但随着时间的流逝,询问我这个问题的人慢慢的变多,逐渐的有些我觉得应该有独立思考能力的人也开始向我确认这件事情的真伪,这让我发生的警惕。现在问我这个问题的人已经不再是确认真伪了,而是直接问我,为什么网易要把运营的好好的游戏卖给日本呢?我已经不能再用哭笑不得的心态来对待这类提问。

三人成虎的故事有它的道理,这道理说起来大家都懂,但没有切身体会还真感受不到那种真切。谣言止于智者,但由于信息不对等的关系,智者也会被蒙骗。慎重其事的澄清谣言不应该是一件不屑一故的事。

这里以我个人的身份,慎重的澄清以下事实:

梦幻西游从始至终都是网易自己在运营,从来没有卖给日本公司。从来没有产生过这种想法,也没有任何日本公司对此表示过兴趣。网易的运营团队没有接触过任何日本公司商谈这类事情。我可以得出这个结论,基于我对公司的了解,对公司游戏部门管理层的同事的了解,对丁磊的了解。而且梦幻西游到今天还是一款给公司带来很高收入的游戏,卖给任何公司(不限于日本公司)来运营都不会给公司带来更多的收益。

关于衙门里的海水朝日图,无论它看起来多像所谓的“太阳旗”,都不存在任何设计者对日本的偏见。我可以得出这个结论,基于我对西游组策划的了解,对美术的了解。梦幻西游在初期创作的时候,场景图片的设定来源于大话西游。而大话西游最初也设计了类似场景,我个人参于了大话西游初期的全程开发。包括审核一些图片,所以对这个问题有发言权。其实,三四年前,就有人反应“太阳旗”问题,当时我也了解到这个信息,并以项目组内部闲聊的形式发表过意见。当时我认为,因为我们没有所谓“亲日”的意思,而且图案的设计又有根源可查。所谓清者自清,浊者自浊,如果我们真的出个补丁去改图,那还真有点做贼心虚的味道了。今天看来,当时我做的论断是错误的。

至于衙门前的 Q 版狮子问题,是原本我看来最可笑的。居然有谣言说,这个狮子形象在七七那天被改成了“猪样”。我还真佩服编写这个谣言的人。这个场景图片从梦幻西游03年第一次发布到今天都没有被修改过的。梦幻西游的场景图片其实是以 jpg 文件形式打包存放,而更新包则是对这些文件的修改。如果有必要的话,可以去核查历史上的所有更新补丁,绝对找不到任何对场景中那块地方的修改。而所有的更新补丁在官方网站都可以找到。

这个图既然从来没有改过,为什么这么多玩家在七七之前和七七之后的观感就不一样了呢。我想可以用《列子 说符》中的那则故事来解释:

一个人斧子丢了,总怀疑是邻居的儿子偷了去,当他看到邻居的儿子时,发现他说话、走路、表情都象是偷了斧子的样子。后来斧子找到了,原来是自己不小心埋进了山谷。这时他再去瞧邻居的儿子,发现他走路、说话、表情一点也不象偷了斧子的样子。


说了这么多,我想就这个事情继续谈点别的。那就是网易在处理客户关系时,以往一定做错了许多。尤其是对渠道商的强势,对玩家的强势,甚至是对广告商和游戏媒体的轻视。这次谣言扩散的这么快,影响面这么广,一定是有一些明白真相的人却故意的推波助澜导致的。另一方面是被轻视的客户久而久之的积怨找到了一个宣泄口,感情上让他们轻信了谣言。这是网易做的非常不够的地方,我想经过这次事件,大家都意识到这些错误了。知错能改,善莫大焉。

November 01, 2006

Windows 和 Unix 下动态链接库的区别

最近慢慢将开发环境转向了 freebsd ,渐渐的发现了许多东西跟 Windows 下不太一样。我指的是,跟我以前想当然的理解不太一样。比如,unix 下对动态链接库 so 的处理,和 Windows 下的 DLL 就不太相同。

之前,我对 Windows 下更为了解一些。早几年做 Windows 开发,老是为动态链接,静态链接这些问题纠缠不清;慢慢的才有了比较清晰的理解,现在基本上不会为这类问题困绕了。前段时间,碰到一些朋友写 lua 的扩展库(windows 版本)时遇到的几个 bug ,就是因为链接问题导致的。公司内部的一个项目,在服务器程序上也碰到了错误链接 lua 引起的 bug ,在内部 maillist 上争论了好久。当时,作为 windows 程序员,跟同事中的 unix 程序员争论看似相同的问题时,却老说不到一起去;今天才发现,原来是相互之间对动态链接库的理解不同导致的。

今天,写下本文,或许可以让以后跨平台开发的朋友少走点弯路。

动态链接库在 unix 下,习惯以 .so 为文件名结尾(通常还以 lib 开头)。而 Windows 下是以 .DLL 为文件后缀。Windows 在处理 dll 上还有一些细节容易被人忽略,我曾经为这个写过一篇 Blog

如果需要运行时主动加载一个动态链接库,windows 下可以使用 LoadLibrary 这个 kernel API (在 kernel32.dll 中);unix 下是用 dlopen 。Windows 下找到 dll 中导出符号的地址,可以用 GetProcAddress ,而 unix 也有对应的 api ...

这些相互对应的 api ,似乎预示着对等的功能,但事实上是有区别的。

DLL 事实上和 EXE 文件一样,同属 PE 格式的执行文件。对于隐式的引用外部符号,需要把外部符号所在的位置写在 PE 头上。PE 加载器将从 PE 头上找到依赖的符号表,并加载依赖的其它 DLL 文件。

但是,unix 上并非如此,so 文件大多为 elf 执行文件格式。当它们需要的外部符号,可以不写明这些符号所在的位置。也就是说,通常 so 文件本身并不知道它依赖的那些符号在哪些 so 里面。这些符号是由调用 dlopen 的进程运行时提供的。而 unix 下的执行文件本身会暴露自己静态链接的符号,(可以是自己本身实现的,或者是从静态库 .a 文件里链入的)。dlopen 将把这些符号通报给 dlopen 加载的 .so 文件,最终完成动态链接。(事实上 dlopen 还可以指定 mode ,完成更复杂的操作)

因为这个区别,unix 下的 lua 解释器可以完全静态链接所有的 lua api ;我们为 lua 扩展的库,以 so 的形式存在被运行时加载不会有任何隐患。而 Windows 下,必须生成一个 luacore 的 DLL 文件,由 lua 解释器于扩展库共享 lua api (还包括 crt 的实现) 才不会出问题。

也因为这个区别,VC 下才会有让 windows 开发新手困惑不解的动态链接 CRT ,静态链接 CRT ,多线程库,单线程库,等等的选项。没点点 windows 开发功力的人,多少都要在上面栽几个跟头。

从动态链接库的这个设计上来看,我个人感觉,Windows 弄的真是糟糕透顶。尤其是对开发者来说是这样。至少我们在 windows 下做一个 dll 文件给大家使用还需要携带一个 .lib 文件;而 unix 下一般只需要有相应的头文件就够了。对于编写新的 .so ,找不到的符号可以就让它在那里,直到最终执行文件来把所有需要的符号联合到一起。windows 可以存在一个 dll 对另一个 dll 的隐式依赖;而 unix 下一般不需要让 so 和 so 有隐式依赖关系。这让我们全局替换类似 CRT 的东西变的困难许多;而以我自己的编程经验来看,好处却没有多少。

顺便一提的是 unix 下需要用 ldconfig 来管理动态库,这比 windows 下 copy DLL 到当前目录下就可以用的方式,无疑提高了系统的安全性。