有位朋友刚开始学编程,所以以下文字写给她的。高手就不用看了~~
关于程序设计的几个基本概念,其实还是蛮重要的。然而很多新手,甚至已经书写过很多程序的老手,他们的这些概念都非常的模糊。
首先说一说高级语言和汇编语言,以及机器语言的区别和联系。很多课本上也都有提到过,但都比较模糊,难以深刻的理解。
实际上,我们的计算机是不懂得我们的语言的(中文,英文等)。而计算机处理数据,只是 1 和 0 两种而已。因此,真正的计算机执行的命令,都是以 0 和 1 这样的数字形式存在的。不同的处理器,他们的执行命令也不同,但基本上已经形成业内的一些规范和指令集。
而机器语言由于都是用数字表示的指令和数据,因此非常的生涩,几乎不会有人直接使用它书写程序。
于是,人们使用更方便理解的,具有一定意义的,文字的命令去一一对应这些机器语言。这样相当于提供了一种人可以接受的机器语言形式,这就是汇编语言。
汇编语言与机器语言的对应关系非常直接,非常简单,这就使得机器语言与汇编语言之间,存在一种很直接的相互转换过程。这就是汇编和反汇编。(汇编指从汇编语言到机器语言,反汇编则相反)(实际上,某些汇编语句还是需要转换的,但绝大多数都可以直接与机器语言对应)
汇编的过程,就是一个汇编程序(比如微软的 MASM)将一个文本形式的汇编源代码(比如 *.asm 文件)翻译成机器语言的过程。
比如有以下的 *.asm 文件代码段:
rA dd ? ;定义 rA 变量
rB dd ? ;定义 rB 变量
rC dd ? ;定义 rC 变量
.code
start:
mov rA , 1 ;为 rA 赋值为 1
mov rB , 2 ;为 rB 赋值为 2
mov eax , rA ;将 rA 装载入 eax 寄存器
add eax , rB ;在 eax 寄存器加上 rB 的值
mov rC , eax ;将相加的结果,也就是 eax 寄存器的值放入 rC 变量
当然,我们的汇编程序并不是死死的要求我们敲入一个又一个的变量地址。汇编程序还有很多功能,比如变量内存管理。
如上代码提到的变量 rA,rB 和 rC 三个变量。汇编程序自动为我们分配了地址。而所有的定义语句,在汇编后将不再存在。
以上的汇编语句,经过汇编以后,就变成了形如以下的机器语言(后面对应了反汇编后的“汇编”表达形式):
C705 00304000 01000000 ;mov dword ptr [403000], 1
C705 04304000 02000000 ;mov dword ptr [403004], 2
A1 00304000 ;mov eax, [403000]
0305 04304000 ;add eax, [403004]
A3 08304000 ;mov [403008], eax
这里的 [403000] 就是 rA 的地址,[403004] 是 rB 的,而 [403008] 是 rC 的。也就是说,经过汇编之后,所有的变量名变成了地址,所有的函数,也变成了地址,所有的常量也变成了它的值。这样,我们在书写汇编语言代码的时候,就不需要去考虑内存地址的问题,简化了我们的工作。而从“变量名”(如 rA,rB,rC)到“内存空间”(如 403000,403004),是由汇编程序去完成分配的。
正因为源代码中的变量都变成了地址,因此我们在拿到别人的可执行文件(如 EXE),再经过反汇编后,我们无法得到原始的源代码。我们看到的,是一堆不可直接理解的内存地址...从 EXE 得到原始的源代码,几乎是不可能的,因为在汇编(高级语言的编译)过程中,很多很多信息都已经丢失了。
上面以一个例子显示了汇编语言与机器语言,以及与反汇编后看到的“汇编语言”的一些区别和联系。其实它们之间还有更多更多的关联。就不再描述了。
由于汇编语言的操作非常细致,因此要完成一个简单的动作,就需要大量的语句。而且书写起来不够直观。因此人们制定了高级语言规范(比如 C,C++,Java,BASIC 等)。并设计开发了高级语言编译器。(这里的编译器区别于汇编语言的汇编程序)
编译器是把高级语言的源代码文件(比如 C 语言的 *.cpp 源代码文件)转换成对应平台的目标代码(机器语言)的程序。
比如以上的汇编代码,如果写成高级语言,则可能很简单的书写为:
rC = rA + rB
经过高级语言的编译器编译之后,得到的目标程序(比如 EXE),它的指令类似与上面描述的一长串机器语言指令。
这样的高级语言书写形式,更符合人们的习惯。也更容易理解和修改。这样的形式也更容易方便的完成一些列的动作。更简单直接。
也因为这样的书写方式更远离机器语言,因此翻译这样的高级语言源代码也就更复杂,设计高级语言编译器的难度就更大。
同时,因为高级语言的书写更简单,因此必然导致同样的高级语言语句,可以有不同的机器语言实现方法。这些方法,有些更好,有些就比较糟糕。如何选择更高效的方法实现高级语言功能,这是不同编译器选择优化的不同考虑。也就是说,同一段高级语言代码,经过不同的编译器编译,得到的目标程序是不同的,而且区别会很大。
同时,由于操作系统的出现,代替了刚开始应用程序的磁盘管理,文件管理,内存管理....使得不同操作系统,不同平台下,需要不同的目标程序。而人们书写代码之后,往往希望能够应用到不同的操作系统或平台。因此,高级语言可以一定程度上满足这种需求。同样的高级语言代码,经过 Windows 下 Win32 编译器编译,可以得到程序的 Win32 版本;经过 .NET 平台编译器编译,可以得到程序的 .NET 版本;在 Linux 下,用 Linux 下的编译器编译,又可以得到程序的 Linux 版本。
当然了,这样的跨平台,只是针对源代码而言的。如果要向另一个平台转移,一般是需要重新编译,重新获得目标程序的。而不是一个高级语言书写的程序,获得的目标程序就可以在不同平台上运行。
实际上,很多程序,在书写的初期,就使用了某些平台的特性(比如涉及注册表的内容,就只能在 Windows 下使用)。这样,就决定了他们只能在某个平台上使用,在其他平台上编译,会出现错误。或者源代码需要一定的改动。当然,这比完全重写要容易得多。
正因为高级语言与汇编/机器语言的对应关系不再是单一的一一对应关系,因此,人们制定了很多高级语言规范(比如 C,C++,Java,BASIC,Java 等),以满足不同的需要。而同一种高级语言规范,全世界各个开发商,又开发了不同的编译器(比如 C++ 的编译器就有微软的 Visual C++,Borland 的 C++ Builder 等)。这些编译器哪个更好,也不是三言两语可以说清楚的。它们都各有各的长处,各有各的应用领域,而每个程序员也都有自己的习惯,需求。
相比汇编语言汇编成机器语言的一一对应来说,编译的过程就有了很高的灵活度。因此各个编译器开发商,都在极力的宣言自己能够快速的生成小巧,精炼的汇编代码,提高目标程序的执行效率。对高级语言代码的最优编译,是编译器不断追求的目标。
作为一个商业的编译器产品来说,除了编译器,更多的,还有为书写代码方便而提供的代码编辑器,编辑环境(IDE),调试器,各种库文件,头文件,说明书,帮助文档,演示代码等。这就使得本来应该很小的编译器(最多几个 MB),在成为一个编译器产品后,变得十分庞大(多达几百 MB,甚至几个 GB)。
对于大多数编译器来说,都有自己面向的高级语言规范和目标平台。比如 Visual C++ 面向 C++ 语言的源代码,目标平台是 Windows 的 Win32 平台。当然有些编译器也可以生成 .NET 平台的目标程序。因为高级语言规范是逻辑上的规定,它是不依赖于平台的,因此语法规范上通常都没有涉及到具体实现的细节。这些细节,是交给编译器去发挥的。因此,大多数人学习一门语言之后,还很奇怪,为什么这些和自己接触到的很多计算机知识(比如注册表,对话框等等)联系不上,自己书写的程序为什么还是一个黑黑的,像 DOS 一样的窗口?
这是因为,类似注册表,对话框,图标这些特征。都是依赖于平台的,比如只有 Windows 操作系统才有注册表,而不同操作系统的对话框或各种按钮,实现的细节不同。语法规范上一般不会对这些做出规定。是商业化的编译器为了让用户使用这些平台相关的特征,才会附带一系列的扩展功能(比如对 Windows API 的支持,对 OpenGL 的支持等)。正是使用它们,才能设计出各种漂亮的界面化的窗口程序。
实际上,这种黑黑的像 DOS 一样窗口的程序,有一个名词,叫做 Console(控制台) 程序。它们区别于有界面的那些 GUI (图形用户界面)程序。我们熟悉的 QQ,迅雷这些商业软件都是 GUI 程序。WinRAR 也是 GUI 程序。
但是,请不要忽视了 Console 程序的地位。我们使用的编译器自身,绝大多数都是 Console 程序; WinRAR 的内核:rar.exe 也是 Console 程序。Console 程序大多数来说,是内涵型的。他们没有华丽的外表,却往往在后台默默的工作,不为人知。
作为学习程序设计来说,我们应当首先对语法规范有足够的了解,对数据结构的理解,对算法的理解,对逻辑层次的理解,这些都是非常重要的。不要因为自己还在设计 Console 程序,而别人已经能够设计漂亮的界面了,就感到自卑,等你熟悉了算法,数据结构,编程思想之后,再去学习附带的扩展的界面化的功能,就会十分的轻松了。