导入自 renren

看到陈骉同学很有感想的一篇神创论与命运日志,觉得近日很久没有看到这样的评论了。想说几句自己的观点。

首先我认为,神创论与宿命论没有多少关联,甚至进化论者相较于神创论者更容易接受宿命论的观点。因为神创论主张意志的存在,人所具有的个体意志与神的意志,因此在神创论者的眼中事件的结果是可以通过意志来改变的,亦即如果我从物理楼11楼跳下,那么我就可以改变自己死亡时间的宿命。上帝的意志同样可以左右事件的结果,也就是所谓的宿命不复存在。而进化论者不承认意志独立于物质世界的存在,你我的思考、行为,都受到物理学法则诸如量子力学的约束,这就引出了北大物理系教授的那句“宇宙中的一切都是可以计算的”,亦即宿命论。如我我选择现在从物理楼上跳下,我这一行为并不是处于个人的独立意志,乃是想证明这一点,亦即我跳楼这一举动是有其背后的动机与原因的,就如同计算机的输入必然导致了输出,宿命的必然终结于此。

其次,关于事件的复杂度所导致的随机化,在大量混沌随机中也存在着如统计学和随机分形学这样的规律,并不是否认宿命的充分理由。

关于神创论的合理性问题。我认为是否相信神的存在只是一个boolean二值问题,它为true为false本身并不重要,重要的是确定它的取值之后得到的推论与结果。如果否认神的存在,如现代数学这样的完美又何以存在,进化论者的解释是事物最终会向着更好更高级的方向发展,产生现代数学乃至现代科学是发展的必然。而这种论调显然有悖于物理中以热力学第二定律为首的,预言事物会随时间推演愈发混乱的论断。更进一步,甚至整个人类、整个生物系统的存在都是有悖于热力学推论的现象,是某种理论只能以“小概率事件”解释的现象。

神创论的核心观点之一,是神的唯一存在性 …

farseerfc.wordpress.com 导入

故障描述: MMC Memory Stick Duo记忆棒未经Adapter适配器,直接插入SD Reader,致使MMC卡入SD Reader中。

栈展开: 某日下午,无课。 忙于数分作业,想查询用手机拍摄的板书照片。 取出手机中的MMC。 未经装配Adapter,直接插入SD Reader。 (A runtime exception was thrown.) 尝试翻转笔记本机身,倒出MMC,未果。(rethrow) 尝试用手指甲取出,未果。(rethrow) 考虑到有“推入反弹”机制,尝试将MMC推入更深,反弹机制由于类型不匹配而失效,未果。(rethrow) (The exception spread across the border of the …

farseerfc.wordpress.com 导入

程序开发原理

——抽象、规格与面向对象设计

Barbara Liskov 、John Guttag 著

杨嘉晨 等译

关于翻译风格:

多年来阅读计算机类的著作及译作,感觉总体的困难在于一大堆没有标准译名的技术术语。由于通行于工业界和学术界的还是英文原名和术语,我决定保留大量的英文术语。这样的翻译风格借鉴于台湾著名的译者和作者侯捷先生。对于译与不译的权衡,主要考虑阅读的流畅,以及读者的理解能力,或许难免带有一些主观色彩。

前言 Preface

构建产品级质量的程序——可以在很长一段时间内使用的程序——众所周知是极其困难的。本书的目标就是改善程序员解决这项任务的效率。我希望读者在阅读本书之后成为一名好程序员。我相信本书的成功在于改善编程技巧,因为我的学生告诉我这已经发生在他们身上。

怎么才算是一名好程序员?是产生整个程序产品的效率。关键是要在每一阶段减少浪费掉的努力。解决的方法包括:在开始编写代码之前就仔细考虑你的实现方案,通过未雨绸缪的方法来编写代码,使用严格的测试在早期发现错误,以及仔细注意模块化编程,这样当错误出现时,只需要改动极少数代码就可以修正整个程序。本书涉及所有这些领域的技术。

模块化编程(Modularity)是编写好程序的关键。把程序分解成许多小模块,每一个模块通过良好定义的狭窄接口和别的模块交互作用 …

farseerfc.wordpress.com 导入

3.2 标号、goto,以及switch的实现

goto语句及标号(label)是最古老的C语言特性,也是最早被人们抛弃的语言特性之一。像汇编语言中的jmp指令一样,goto语句可以跳转到同一函数体中任何标号位置:

void f()

{int i=0;

Loop: //A label

++i;

if(i<10)goto Loop; //Jump to the label

}

在原始而和谐的早期Fortran和Basic时代,我们没有if then else,没有for和while,甚至没有函数的概念,一切控制结构都靠goto(带条件的或无条件的)构件。软件工程师将这样的代码称作“意大利面条”代码。实践证明这样的代码极容易造成混乱。

自从证明了结构化的程序可以做意大利面条做到的任何事情,人们就开始不遗余力地推广结构化设计思想,将goto像猛兽一般囚禁在牢笼 …

farseerfc.wordpress.com 导入

3.1 左值右值与常量性(lvalue,rvalue & constant)

首先要搞清楚的是,什么是左值,什么是右值。这里给出左值右值的定义:

1、左值是可以出现在等号(=)左边的值,右值是只能出现在等号右边的值。

2、左值是可读可写的值,右值是只读的值。

3、左值有地址,右值没有地址。

根据左值右值的第二定义,值的左右性就是值的常量性——常量是右值,非常量是左值。比如:

1=1;//Error

这个复制操作在C++中是语法错误,MSVC给出的错误提示为“error C2106: '=' : left operand must be l-value”,就是说’=’的左操作数必须是一个左值,而字面常数1是一个右值。可见,严格的区分左值右值可以从语法分析的角度找出程序的逻辑错误。

根据第二定义,一个左值也是一个右值 …

farseerfc.wordpress.com 导入

2.2 I386平台的内存布局

众所周知,I386是32位体系结构。因此对于绝大多数I386平台的C++编译器而言,sizeof(int)=sizeof(long)=sizeof(void*)=4。当然C++标准对此没有任何保证,我们也不应该试图编写依赖于此的代码。

32位指针的可寻址空间为4GB。为充分利用这么大的寻址空间,也是为了支持其它更先进的技术比如多任务技术或者动态链接库技术,WinNT使用虚拟内存技术,给与每个应用程序全部4GB的内存空间。4GB的地址被一分为二,前2GB供应用程序自己使用,后2GB由系统内核分配和管理。这2GB的内存地址,通常被划分成3种内存区使用:

1 代码及静态数据区

由代码加载器从动态链接库镜像(通常是exe或dll文件)加载,通常定位到镜像文件中指定的基址开始的内存区。如果基址所在内存已被占用,动态连接器会将代码或数据重定向到其它可用地址。

在C++中,静态数据包括:名字空间(namespace)和全局(global)对象、函数的static对象、类的static数据成员 …

farseerfc.wordpress.com 导入

C++ Tricks

By  FarseerFc

从今天起,我再将在Live SpaceQQZone同时发表一系列文章,暂定名为“C++Tricks”。

本文旨在记录和阐述一些本人学习C++时所得的心得、技巧。总体来看,本文涉及的内容是每一个C++程序员都应该知道的,但是很少见诸C++教材。希望对各位同仁学习C++有所帮助。

也可以通过QQ或MSN向我索要此文的DOC版或PDF版,会比网页上的更新的快一点。

1      词法问题(Lexical Problems)


2      X86体系结构

     2.1 X86概述

     2.2 …

farseerfc.wordpress.com 导入

2.3 I386平台C函数内部的栈分配

函数使用栈来保存局部变量,传递函数参数。进入函数时,函数在栈上为函数中的变量统一预留栈空间,将esp减去相应字节数。当函数执行流程途径变量声明语句时,如有需要就调用相应构造函数将变量初始化。当执行流程即将离开声明所在代码块时,以初始化的顺序的相反顺序逐一调用析构函数。当执行流程离开函数体时,将esp加上相应字节数,归还栈空间。

为了访问函数变量,必须有方法定位每一个变量。变量相对于栈顶esp的位置在进入函数体时就已确定,但是由于esp会在函数执行期变动,所以将esp的值保存在ebp中,并事先将ebp的值压栈。随后,在函数体中通过ebp减去偏移量来访问变量。以一个最简单的函数为例:

void f()

{

int a=0; //a的地址被分配为ebp-4

char c=1; //c的地址被分配为ebp-8

}

产生的汇编代码为:

push ebp ;将ebp压栈

mov ebp,esp ;ebp=esp 用栈底备份栈顶指针

sub …

farseerfc.wordpress.com 导入

2.4 I386平台C函数调用边界的栈分配

当调用一个函数时,主调函数将参数以声明中相反的顺序压栈,然后将当前的代码执行指针(eip)压栈,然后跳转到被调函数的入口点。在被调函数中,通过将ebp加上一个偏移量来访问函数参数,以声明中的顺序(即压栈的相反顺序)来确定参数偏移量。被调函数返回时,弹出主调函数压在栈中的代码执行指针,跳回主调函数。再由主调函数恢复到调用前的栈。

函数的返回值不同于函数参数,通过寄存器传递。如果返回值类型可以放入32位变量,比如int、short、char、指针等类型,通过eax寄存器传递。如果返回值类型是64位变量,如_int64,同过edx+eax传递,edx存储高32位,eax存储低32位。如果返回值是浮点类型,如float和double,通过专用的浮点数寄存器栈的栈顶返回。如果返回值类型是用户自定义结构,或C++类类型,通过修改函数签名,以引用型参数的形式传回。

同样以最简单的函数为例:

void f(){

int i …

farseerfc.wordpress.com 导入

2.5 I386平台的边界对齐(Align)

首先提问,既然I386上sizeof(int)==4、sizeof(char)==1,那么如下结构(struct)A的sizeof是多少?

struct A{int i;char c;};

答案是sizeof(A)==8……1+5=8?

呵呵,这就是I386上的边界对齐问题。我们知道,I386上有整整4GB的地址空间,不过并不是每一个字节上都可以放置任何东西的。由于内存总线带宽等等的技术原因,很多体系结构都要求内存中的变量被放置于某一个边界的地址上。如果违反这个要求,重则导致停机出错,轻则减慢运行速度。对于I386平台而言,类型为T的变量必须放置在sizeof(T)的整数倍的地址上,char可以随便放置,short必须放在2的整数倍的地址上,int必须放在4的整数倍的地址上,double必须放在8的整数倍的地址上。如果违反边界对齐要求 …

farseerfc.wordpress.com 导入

2.6 I386平台C函数的可变参数表(Variable Arguments)

基于前文(2.4节)分析,我们可以不通过函数签名,直接通过指针运算,来得到函数的参数。由于参数的压栈和弹出操作都由主调函数进行,所以被调函数对于参数的真实数量不需要知晓。因此,函数签名中的变量声明不是必需的。为了支持这种参数使用形式,C语言提供可变参数表。可变参数表的语法形式是在参数表末尾添加三个句点形成的省略号“...”:

void g(int a,char* c,...);

省略号之前的逗号是可选的,并不影响词法语法分析。上面的函数g可以接受2个或2个以上的参数,前两个参数的类型固定,其后的参数类型未知,参数的个数也未知。为了知道参数个数,我们必须通过其他方法,比如通过第一个参数传递:

g(3,”Hello”,2,4,5);//调用g并传递5个参数,其中后3个为可变参数。

在函数的实现代码中,可以通过2.4节叙述的 …

farseerfc.wordpress.com 导入

2.7 I386平台的其它函数调用模型

上文介绍的只是I386平台上C函数调用的标准模型,被称作__cdecl。事实上,Microsoft Visual C++编译器还支持其它一些函数调用模型,所有调用模型名称皆以双下划线开头,下面列出所有函数调用模型的异同:

1 __cdecl

参数压栈顺序:逆序(从右至左)

参数堆栈恢复者:主调函数(caller)

__cdecl明确地指出函数使用C函数调用模型,这是默认的调用模型。

2 __stdcall

参数压栈顺序:逆序(从右至左)

参数堆栈恢复者:被调函数(callee)

__stdcall是微软所谓的标准调用模型。可惜的是它与__cdecl不兼容。几乎所有的Win32API函数使用这种函数调用模型,希望在DLL之间,或者在程序和WinNT操作系统之间传递函数指针的函数也应该使用这种模型。与__cdecl模型的不同之处在于,__stdcall模型下由被调函数恢复堆栈。主调函数在call语句之后,不需要再加上add语句。而被调函数的ret语句则被添加一个参数,代表函数参数堆栈的长度。因此,被调函数需要明确的知晓函数参数的数量和类型,所以在__stdcall模型下不支持可变参数表,所有参数必须写明 …