计算机源文件及编译系统、Unix编译指令
---------------hello.c-------------
#include
int main()
{ printf("hello, world\n");
return 0;}
-------------------------------------
信息就是位+上下文
hello 程序的生命周期是从一个源程序(或者说源文件)开始的,即程序员通过编辑器创建并保存的文本文件,文件名是hello.c。源程序实际上就是一个由值0和1组成的位(又称为比特)序列,8 个位被组织成一组,称为字节。每个字节表示程序中的某些文本字符。
大部分的现代计算机系统都使用ASCII 标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。
hello.c 程序是以字节序列的方式储存在文件中的。每个字节都有一个整数值,对应于某些字符。例如,第一个字节的整数值是35, 它对应的就是字符“'# '。第二个字节也整数值为105, 它对应的字符是'i',依此类推。注意,每个文本行都是以一个看不见的换行符'\n' 来结束的,它所对应的整数值为10。像hello.c 这样只由ASCII 字符构成的文件称为文本文件,所有其他文件都称为二进制文件。
hello .c 的表示方法说明了一个基本思想:系统中所有的信息—包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。
程序被其他程序翻译成不同的格式
hello程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂。然而,为了在系统上运行hello.c 程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令。然后这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件。
在Unix 系统上,从源文件到目标文件的转化是由编译器驱动程序完成的:
linux> gcc -o hello hello .c
在这里,GCC编译器驱动程序读取源程序文件hello.c, 并把它翻译成一个可执行目标文件hello。这个翻译过程可分为四个阶段完成,如图1-3 所示。执行这四个阶段的 程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统(compilation system)。
1、预处理阶段:Hello.c经过预处理器cpp,根据以字符#开头的命令语句,修改原始的C程序。比如#include命令告诉预处理器读取系统文件的stdio.h的内容插入程序文本中,插入所有用#include 命令指定的文件,并扩展所有用#define 声明指定的宏。得到hello.i文件。
2、编译阶段:编译器ccl将文本hello.i翻译程文本文件hello.s,它包含一个汇编语言程序。
3、汇编阶段:汇编器(as)将hello.s 翻译成机器语言指令,把这些指令打包成 一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标 文件hello.o 中。hello.o 文件是一个二进制文件,它包含的17 个字节是函数main 的指令编码。如果我们在文本编辑器中打开hello.o 文件,将看到一堆乱码。
4、链接阶段。请注意,hello 程序调用了printf 函数,它是每个C编译器都提供的标准C库中的一个函数。printf 函数存在于一个名为printf .o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的hello.o 程序中。链接器(Id)就负责处理这种合并。结果就得到hello文件,它是一个可执行目标文件 (或者简称为可执行文件),可以被加载到内存中,由系统执行。
旁注 GNU项目:GCC 是GNU(GNU 是GNU’S Not Unix 的缩写)项目开发出来的众多有用工具之一。GNU 项目是1984 年由Richard Stallman 发起的一个免税的慈善项目。该项目的目标非常宏大,就是开发出一个完整的类Unix 的系统,其源代码能够不受限制地被修改和传播。GNU 项目已经开发出了一个包含Unix 操作系统的所有主要部件的环境,但时核除外,内核是由Linux 项目独立发展而来的。GNU 环境包括EMACS 编辑器、GCC编译器、GDB 调试器、汇编器、链接器、处理二进制文件的工具以及其他一些部件。GCC 编译器已经发展到支持许多不同的语言,能够为许多不同的机器生成代码。支持的语言包括C、C++ 、Fortran、Java、Pascal、面向对象C 语言(Objective~C)和Ada。
处理器读并解释储存在内存中的指令
hello .c 源程序已经被编译系统翻译成了可执行目标文件hello, 并被存放在磁盘上。要想在Unix系统上运行该可执行文件,我们将它的文件名输入到称为shell的应用程序中:
linux> . /hello
hello, world
linux>
shell 是一个命令行解释器,它输出一个提示符,等待输人一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的shell 命令,那么shell 就会假设这是一个可执行文件的名字,它将加载并运行这个文件。所以在此例中,shell 将加载并运行hello 程序,然后等待程序终止。hello 程序在屏幕上输出它的消息,然后终止。shell随后输出一个提示符,等待下一个输入的命令行。
(一)假设一个C程序,有两个文件p1.c 和p2.c。我们用Unix 命令行编译这些代码:
linux> gcc -Og -o p p1.c p2 .c
命令gcc指的就是GCC C 编译器。因为这是Linux上默认的编译器,我们也可以简单地用cc来启动它。编译选项-Og 告诉编译器使用会生成符合原始C代码整体结构的机器代码的优化等级。
(二)假设我们写了一个C 语言代码文件mstore.c,包含如下的函数定义:
-------------------------mstore.c-------------
long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t; }
----------------------------------------------
在命令行上使用“-s” 选项,就能看到C 语言编译器产生的汇编代码:
linux> gcc -Og -S mstore.c
这会使GCC 运行编译器,产生一个汇编文件mstore.s,但是不做其他进一步的工作。
如果我们使用“ -c” 命令行选项,GCC 会编译并汇编该代码:
linux> gcc -Og -c mstore.c
这就会产生目标代码文件mstore.o, 它是二进制格式的,所以无法直接查看
要查看机器代码文件的内容,有一类称为反汇编器(disassembler ) 的程序非常有用。 这些程序根据机器代码产生一种类似于汇编代码的格式。在Linux 系统中,带‘-d’命令行 标志的程序0BJDUMP(表示“ object dump”)可以充当这个角色:
linux> objdump -d mstore.o
生成实际可执行的代码需要对一组目标代码文件运行链接器,而这一组目标代码文件 中必须含有一个main 函数。假设在文件main.c 中有下面这样的函数:
--------------------main.c--------------------
#include
void multstore(long, long, long *);
int main(){
long d;
multstore(2, 3, fed);
printf("2 * 3 > %ld\n", d);
return 0; }
long mult2(long a, long b) {
long s = a * b;
return s;}
----------------------------------------------
然后,我们用如下方法生成可执行文件prog:
linux> gcc -Og -o prog main.c mstore.c
我们也可以反汇编prog 文件:
linux> objdump -d prog