《编程高手箴言》读书笔记

来源:互联网 发布:淘宝销量权重 编辑:程序博客网 时间:2024/05/17 04:32

书名:编程高手箴言

作者:梁肇新

 

 内容简介:

 本书是作者十余年编程生涯中的技术和经验的总结。内容涵盖了从认识CPU、Windows运行机理、编程语言的运行机理,到代码的规范和风格、分析方法、调试方法和内核优化,内有作者对许多问题的认知过程和透彻的分析,以及优秀和精彩的编程经验。 

 

第1章程序点滴

 

1.1程序≠软件

 能够产生商业意义的程序才能叫做软件。

 

共享软件是避开商业渠道的一种方法。

 

现在商业软件的门槛已经很高了。

 

做一个程序员一定要有耐心,因为现在已经不等于以前了。你一定要把所有的问题搞清楚,然后再去做程序。

对于程序员来说,最好的工作环境是在现有的或者初始要成立的公司里面,这是最容易成功的。个人单枪匹马闯天下已经很困难了。

 

商业软件往往是由很多模块组成的,模块是整个系统的一部分。个人要完整地写一个商业系统几乎是不可能的。软件进入Windows平台后,它已经很复杂了。

 

不要做别人已经成熟的东西。

 

如果你仅仅只是想混口饭吃,找个工作,可能教你成为MFC的高手之类的书对你就足够了。但是,如果你想做一个很好的软件,不仅能满足你谋一碗饭吃,还能使你扬名,最后你的软件还能成为很多人用,甚至你还想把它作为一个事业去经营,那么这第一步就非常关键。这时就绝对不能找一本MFC或找一本VB的书学两下就行,而是要从最低层开始做起,从最基本做起。

 

1.2高手是怎样练成的

1.2.1  高手成长的六个阶段

我认为,一个程序员的成长可分为如下六个阶段。

 

Ø         第一阶段

 

此阶段主要是能熟练地使用某种语言。这就相当于练武中的套路和架式这些表面的东西。

 

Ø         第二阶段

 

此阶段能精通基于某种平台的接口(例如我们现在常用的Win 32的API函数)以及所对应语言的自身的库函数。到达这个阶段后,也就相当于可以进行真实散打对练了,可以真正地在实践中做些应用。

 

Ø         第三阶段

 

此阶段能深入地了解某个平台系统的底层,已经具有了初级的内功的能力,也就是“手中有剑,心中无剑”。

 

Ø         第四阶级

 

此阶段能直接在平台上进行比较深层次的开发。基本上,能达到这个层次就可以说是进入了高层次。这时进入了高级内功的修炼。比如能进行VxD或操作系统的内核的修改。

 

这时已经不再有语言的束缚,语言只是一种工具,即使要用自己不会的语言进行开发,也只是简单地熟悉一下,就手到擒来,完全不像是第一阶段的时候学习语言的那种情况。一般来说,从第三阶段过渡到第四阶段是比较困难的。为什么会难呢?这就是因为很多人的思想转变不过来。

 

Ø         第五阶级

 

此阶段就已经不再局限于简单的技术上的问题了,而是能从全局上把握和设计一个比较大的系统体系结构,从内核到外层界面。可以说是“手中无剑,心中有剑”。到了这个阶段以后,能对市面上的任何软件进行剖析,并能按自己的要求进行设计,就算是MS Word这样的大型软件,只要有充足的时间,也一定会设计出来。

 

Ø         第六阶级

 

此阶段也是最高的境界,达到“无招胜有招”。这时候,任何问题就纯粹变成了一个思路的问题,不是用什么代码就能表示的。也就是“手中无剑,心中也无剑”。

 

此时,对于练功的人来说,他已不用再去学什么少林拳,只是在旁看一下少林拳的对战,就能把此拳拿来就用。这就是真正的大师级的人物。这时,Win32或Linux在你眼里是没有什么差别的。

 

每一个阶段再向上发展时都要按一定的方法。第一、第二个阶段通过自学就可以完成,只要多用心去研究,耐心地去学习。

 

要想从第二个阶段过渡到第三个阶段,就要有一个好的学习环境。例如有一个高手带领或公司里有一个好的练手环境。经过二、三年的积累就能达到第三个阶段。但是,有些人到达第三个阶段后,常常就很难有境界上的突破了。他们这时会产生一种观念,认为软件无非如此,认为自己已无所不能。其实,这时如果遇到大的或难些的软件,他们往往还是无从下手。

 

现在我们国家大部分程序员都是在第二、三级之间。他们大多都是通过自学成才的,不过这样的程序员一般在软件公司也能独当一面,完成一些软件的模块。

1.2.2  初级程序员和高级程序员的区别

初级程序员考虑用什么方法才能做出来。

中级程序员考虑用什么容易的方法做出来。

高级程序员考虑怎样才是效率最高、性能最稳定。

 

一个高级程序员应该具备开放性思维,从里到外的所有的知识都能了解。然后,看到世界最新技术就能马上掌握,马上了解。实际上,技术到达最高的境界后,是没有分别的。任何东西都是相通的,只要你到达这个境界以后,什么问题一看就能明白,一看就能抓住最核心的问题,最根本的根本,而不会被其他的枝叶或表象所迷惑,做到这一步后才算比较成功。

 

如果你越有野心,你就越要有耐心,你的野心才有可能实现。

1.2.3  程序员是吃青春饭的吗

不是

 

时刻跟上技术的步伐。

水平越高,他就看得越远,那么他的思维就越开阔;水平越低,想的问题就越窄。

 

做程序员一定要有一种正常的心态,就是说,你做程序的时候,不要把自己的生活搞得颠三倒四的。疲劳战或者是黑白颠倒,时间长久后就玩不转了,玩着玩着就不想玩了。

 

一旦落后,你再想追,就很难了。

 

1.3正确的入门方法

入门最基本的方法就是从C语言入手

现在的C语言本身就包含了嵌入汇编,使学习汇编语言的时候更加方便。

 

如果你准备花5年的时间成为高手,那我敢说,你根本不用等到5年,你只要有这个耐心就足够了,你可能2年~3年内就能达到目标。但如果你想在一年时间内就成为高手,即使5年后,你还是成不了高手。

 

厚集薄发。

 

所有的语言只是很花哨的表面东西。

 

当你成为C语言的高手,那么就你很容易进入到操作系统的平台里面去;当你进入到操作系统的平台里去实际做程序时,就会懂得进行调试;当你懂得调试的时候,你就会发现能轻而易举地了解整个平台的架构

 

如果你真正有一种开放性的思维,在你能够成为高级程序员的时候,对MFC这些是不屑一顾的,MFC、VB根本不会在考虑的范围之内。

 

1.3.1  规范的格式是入门的基础

1. 成对编码

{}

分配内存后立刻写释放内存,然后在中间插入使用内存的代码

 

集成环境的TAB首先要设成8,因为TAB的基本定义就是8,而现在的VC把它设成了4,这样使得你编出的程序放到一个标准的环境里看的时候就是乱的。

 

而且结合成对编码思维,这时候你去读一个程序的时候,你会发现,你读程序的方法变了。以前读程序的时候,你可以先去读它的变量是什么,然后再读第一行、第二行,读到最后一个大括号,这是一种读程序的方法。现在就不一样了,现在读程序的时候就养成了一种习惯,就是分块阅读程序,很明显两个大括号之间就是一块代码。

 

那么写出一个程序后,你要读这个程序是干什么的,只要看这个大括号和那个大括号之间的部分就可以了,不需要再去读其他的代码是干什么的。

 

代码中如果不包括正确的思路,那该代码就没有什么用。

 

2. 代码的注释

代码本身体现不出价值来,有价值的代码一定是不仅格式非常规范,而且还要有很详细的设计思路和注释,这个是很重要的。首先要养成这种习惯.

 

1.3.2  调试的重要性

所有的程序都是调试出来的,不是写出来的。

用VB或者是MFC做出来的程序,先运行一遍看看什么地方有问题,如果发现有问题,重新改一改,然后又重新运行。这种方法是还没有入门的调试方法,即是看直接的表象。这种方法既浪费时间,又不能消除隐患。

 

如果要成为高级程序员,就必须过这一关。如果不懂调试,则永远成不了高手。在学习调试的过程中,对汇编语言、体系结构会有进一步的了解。

如果说哪个系统是编出来的,那它肯定会有很多性能方面的问题,包括可能有不可预测的各种各样的问题。

 

1.4 开放性思维

要具备开放性思维,就必须了解包括从CPU的执行方法,到Windows平台的运转,到你的程序的调试,最后到你要实现的功能这一整套的内容,只有做到这样,才能真正提高。如果你的知识范围很窄,什么也不了解,纯粹只了解语言,那你的思维就会很狭隘,就会只想到这个语言有这个函数,那个语言没有那个函数,这个C++有这个类,那个语言没有这个类等。而真正要做一个系统,思维一定要是全面的,游离于平台之上的系统和实际的应用软件是不现实的。

 

任何一个软件一定都是跟一个平台相关联的,脱离平台之上的软件几乎都是不能用的。这就必须对平台的本身非常了解。如果你有平台这些方面的知识,这样在思考一个问题的时候,能马上想到操作系统能提供些什么功能我再需要做些什么,然后就能达到这个目标。这就是一种开放的思维。

 

在开放的思维下,我要做这个程序的时候,就会考虑怎么把它拆成几个独立的、分开的模块,最简单的,怎么把这个模块尽量能单独调用,而不是我要做个很大的EXE程序。一个很普通的程序员,如果他能够考虑到将程序分成好几个动态库,那么它的思维就已经有点开放性了,就已经不是MFC那些思维方式了思考问题的时候能把它拆开,就是说,任何一个问题,如果你能把它拆开来思考,这就是简单的开放性思维。

 

但光会拆还是不够的,尽管有很多人连拆都不会。很多教科书中的程序,要解决问题的时候,就一个main,以后就是一个非常长的函数。这个main函数把所有的事情都解决了。如果连函数都不会分的话,则就是典型的封闭式思维。

 

比如最简单的,哪个函数越大,则该函数的出错几率就越大。但如果把该函数分成很多小的函数,每个小的函数的出错几率就会很小,那么组合起来的整个程序的出错几率就很小。这就是为什么要把它拆出来的原因。

 

1.4.1  动态库的重要性

有了动态库,当你要改进某一项功能的时候,你可以不动任何其他的地方,只要改其中你拆出来的这一块。

 

1.4.2  程序设计流程

第一步就是要拆出模块.要把它拆成一个个的独立的模块;最后,再进一步去实现,从小到大地进行设计。

 

首先“抓”马上能进行测试的简单的模块,就像刚才说的成对编码那样,写任何一个部分都要进行调试,每个部分最好能独立进行调试。这样,每个部分都是分开的时候,它都有一定的功能。当把所要做的功能都实现后,组合起来,再进行通调就可以了。

1.4.3  保证程序可预测性

你必须从最低层到最上层都要清楚。VC本身提供了一个汇编的调试环境,但是打开汇编后,如果你都看不懂,那你说怎么调呢?调什么?

 

总结:底层、耐心、开放性思维

 

第2章认识CPU

由此可见,机会永远是留给有心人的。

 

8086CPU有20根地址线,可直接寻址的物理地址空间为1MB。

地址线的根数决定CPU可以访问多大的物理内存。因为现在的CPU有32根地址线,所以,我们的内存不能大于4G,就算大了也没有意义。

 

尽管8086/8088内部的ALU每次最多进行16位运算,但存放存储单元地址偏移的指针寄存器都是16位的,所以8080/8086通过内存分段和使用段寄存器的方法来有效地实现寻址1MB的空间。

 

物理地址=段值(64K)×16+偏移(64K)(好像不止1M了???)

 

位数和CPU地址线应该没有关系!!

位数和寄存器的位数没有关系!!因为指令又不放到寄存器中。

 

多少位CPU是指CPU所能执行的指令的最大长度,如果CPU的位数越高,那么它执行的指令的复杂度就可以更高,功能当然就更强。本来CPU有20根地址总线可以寻址1M的物理内存,但是寄存器是16位的,而CPU访问哪个内存的地址存储在寄存器中的,所以它还是不能直接访问1M内存。

 

8086都有哪些寄存器?都用来干什么?寄存器和CPU缓存有什么联系没有?

 

CPU是如何执行指令的?

 

2.2.4  中断处理

 

中断使CPU暂停正在运行的事件而转去处理另一事件。其实,中断还可以认为是一种函数的调用,不过,这个函数是随时都可能调用的,这样,中断就很好理解了。我们把引起这种操作的事件就叫中断源。它们可以是外设的输入输出请求,也可是计算机的一些异常事件或者其他的内部原因。

 

在8086/8088的计算机中,支持256种类型的中断,其中断编号依次为0x00~0xFF。

 

每种中断都有一个中断处理程序与之相对应(也就是说一个中断发生必然导致一个与中断号对应的函数被CPU调用)。这些处理程序的段值和偏移量(也就是说处理程序的函数地址就等于段值(64K)×16+偏移(64K),因为寻址的原因)都被安排在内存的最顶端。因为它们占用1KB字节空间(256×4,段值占两个字节,偏移量占两个字节),所以当发生中断时,CPU根据中断向量表就可以很快地查找到对应的处理程序来处理中断事件。

想要直观的话,就看图2.5中断向量表:

 

 

我们从图中可以看到,通过中断号CPU就可以找到处理函数的地址。例如,假如中断0x00发生,CPU就到内存的4*0x00这个地址去找到处理函数的内存地址(内存4*0x00中存放的值就是代表处理函数的地址),然后调用。

 

中断有没有优先级?也就是说CPU正在处理一个中断的时候,另一个中断发生了,CPU该怎么办?

 

在IBMPC系列兼容计算机中,中断分为两种,一种是可屏蔽中断,另一种是不可屏蔽中断。DOS的部分中断分配情况如表2.1所示。

 

表2.1  DOS的部分中断分配表

 

 

2.332位微处理器

80286芯片能在实模式和保护模式两种方式下工作。在保护模式下,每个同时运行的程序都在分开的空间内独自运行。

实模式和保护模式有什么联系和区别?现在还有程序运行在实模式下吗?

 

2.3.2  保护模式

在保护模式下,不仅可寻址4GB的内存空间,扩充了内存的分段管理机制,并可对内存进行分页管理,而且还可实现虚拟内存,支持多任务。

保护模式最重要的是完善了多任务保护机制。

 

(1)不同任务之间的保护:通过把每个不同的任务放在不同的虚拟地址空间中,来实现不同任务间的隔离(即A程序不能访问和修改B程序的代码和数据,虽然虚拟地址值相同),以达到程序间的隔离。

 

(2)同一任务的保护:在每一任务之内定义了4种保护级别。分别为0、1、2、3,按环的方式来表示,如图2.8所示。

其中,0级代表最高的权限级,3级代表最低的权限级。按环的方式来表示,数字小的在“内环”,数字大的在外环。其中,环0、1、2为系统级,环3为用户级。原来的系统都是基于用户和系统来设计的,所以一般的系统只使用环0和环3这两个级。

 

2.3.3  80386的寻址方式

实模式下,段基地址仍然是16的倍数,段的最大长度仍然是64KB。段寄存器内所含的仍然是段基地址对应的段值,存储单元的物理地址仍然是段寄存器内的段值乘上16再加上偏移。所以,尽管386有32根地址线,可直接寻址物理地址空间达到4GB字节,但在实模式下,仍然与8086/8088相似,只能访问1M的物理内存。

 

是不是在实模式下应用程序访问的都是物理地址?而在保护模式下应用程序访问的都是虚拟地址?

 

在保护模式下,段基地址可长32位,并且无需是16的倍数,可以是内存内任意一个开始点,段的最大长度可达4GB。它的寻址就与8086/8088有很大的变化,如图2.9所示。

虚拟地址=段基地址(32位)+偏移地址(32位)?

 

在保护模式下如何找到物理地址?

 

数据总线宽度、外部总线宽度、地址总线宽度有什么区别和联系?

数据总线宽度是指在芯片内部数据传送的宽度,外部数据宽度是指芯片内部与芯片外部交换数据的宽度;地址总线宽度是指专门用来传送地址的总线宽度。

字长的增加有利于提高计算机计算的精度???

 

现在我们如何进入实模式?

 

主频和外频有什么区别?

 

学会在C语言中写汇编程序???见MSDN

 

例1.CallingC Functions in Inline Assembly(嵌入式汇编)

#include<stdio.h>

 

charformat[] = "%s %s\n";

charhello[] = "Hello";

charworld[] = "world";

voidmain( void )

{

   __asm

   {

      mov eax, offset world

      push eax

      mov eax, offset hello

      push eax

      mov eax, offset format

      push eax

      call printf

      //clean up the stack so that main canexit cleanly

      //use the unused register ebx to do thecleanup

      pop ebx

      pop ebx

      pop ebx

   }

}

总结:以后在看程序的时候头脑中默认加上所有·的寄存器!!!函数调用时的压栈顺序!!!push多少pop多少!!!

 

例2:CallingC++ Functions in Inline Assembly

只能调用全局的没有重载的C++函数;当然Youcan also call any functions declared with extern "C" linkage. Thisallows an __asm block within a C++ program to call the C library functions,because all the standard header files declare the library functions to haveextern "C" linkage.

不能调用类的成员函数。

我理解的原因:

通过这个call printf汇编语句必须能解析出唯一的执行指令。也就是说它在解析printf的时候,是按extern"C"翻译的。

如果printf重载了的话,C++编译器在对printf编译时不会以extern "C"方式翻译,而是以C++的方式翻译。所以call printf根本找不到任何匹配的函数。不重载C++编译器则以extern "C"方式翻译。

对成员函数也是同样道理,汇编根本不能翻译b.fun之类的东西,在汇编中就没有这些东西。b.fun是C++独有的。

 

第3章Windows运行机理

3.1内核分析

DOS是一个开放的操作系统,应用程序和操作系统在同一个级别上,所以应用程序能控制整个机器的所有资源。

 

稍微理解一下什么是VxD、VMM?

 

对VxD讲了很多,但是现在的Windows2000/XP基本上都使用WMD,所以我没有看!!

 

纠正一下他的错误:

正确如下:

当消息队列中有消息时,它会立刻返回,如果消息是WM_QUIT,返回值是0,否则是非0;没有消息时GetMessage就会一直停止,直到有消息为止。

 

当向显示缓存区中写入数据时,就会显示相应的图像。

如果需要快速地显示图像,就不能用GDI,而应直接使用DirectDraw。

 

如果显示卡的内存比较大,有一块显存区域(主显存)是映像到屏幕上的可见区域,还有的显存区域是屏幕上看不见的,这个区域被称为offscreen(次显存)。先在次显存绘制好图形,当需要显示时,马上将次显存切换为主显存。DirectDraw中有一个这种操作函数,这个命令如果能切换,就直接切换,如果不能直接地切换,就直接通过显示卡,从次显存复制到主显存,这种在显卡内的复制要比软件的memcpy命令快很多。

对DirectDraw的具体解释见MSDN.

 

“Portable Executable”意味着此文件格式是跨Win32平台的:即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。

PE头标包含了至关重要的一些信息,诸如代码和数据区的位置和大小、该文件要用什么操作系统以及初始的堆栈大小。

dumpbin.exe

 

原创粉丝点击