经过前面的介绍之后,你应该比较清楚的知道源码文件、编译程序、函式库与可执行文件之间的相关性了。不过,详细的流程可能还是不很清楚,所以,在这里我们以一个简单的程序范例来说明整个编译的过程!赶紧进入 Linux 系统,实地的操作一下下面的的案例!
单一程序:印出Hello World
我们以 Linux 上最常见的 C 语言来编写第一个程序!第一个程序的作用就是在屏幕上面印出 Hello World
的字样。当然,这里我们是以简单的 C 语言来编写,好了,不啰嗦,开始吧!
请先确认你的 Linux 系统里面已经安装了 gcc 了。
如果你已经有网络了, 那么直接使用
yum groupinstall "Development Tools"
预安装好所需的所有软件即可。
编辑程序代码,亦即源代码
编辑 hello.c
文件,内容如下:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
}
开始编译与测试执行
[root@localhost test]# gcc hello.c
[root@localhost test]# ll
总用量 16
-rwxr-xr-x. 1 root root 8440 10月 24 15:53 a.out
-rw-r--r--. 1 root root 67 10月 24 10:44 hello.c
[root@localhost test]# ./a.out
Hello World
我们直接以 gcc 编译源代码,并且没有加上任何参数,则执行档的档名会被自动设定为 a.out
这个文件名!所以你就能够直接执行 ./a.out
这个执行档啦!上面的例子很简单吧!那个 hello.c
就是原始码,而 gcc 就是编译程序,至于 a.out
就是编译成功的可执行 binary program。
咦!那如果我想要产生目标文件(object file)来进行其他的动作,而且执行档的档名也不要用预设的 a.out
,那该如何是好?其实你可以将上面的第 2 个步骤改成这样:
[root@localhost test]# gcc -c hello.c
[root@localhost test]# ll
总用量 8
-rw-r--r--. 1 root root 67 10月 24 10:44 hello.c
-rw-r--r--. 1 root root 1496 10月 24 15:59 hello.o
[root@localhost test]# gcc -o hello.out hello.o
[root@localhost test]# ll
总用量 20
-rw-r--r--. 1 root root 67 10月 24 10:44 hello.c
-rw-r--r--. 1 root root 1496 10月 24 15:59 hello.o
-rwxr-xr-x. 1 root root 8440 10月 24 15:59 hello.out
[root@localhost test]# ./hello.out
Hello World
这个步骤主要是利用 hello.o
这个目标文件制作出一个名为 hello.out
的执行文件。这个动作后,我们可以得到 hello.out
及hello.o
两个文件,真正可以执行的是 hello.out
这个 binary program 喔~或许你会觉得, 咦!只要一个动作编译出 a.out
就好了,干嘛还要先制作目标文件再做成可执行文件呢?看完下个示例,你就可以知道为什么啦!
主、子程序链接:子程序的编译
如果我们在一个主程序里面又呼叫了另一个子程序呢?这是很常见的一个程序写法,因为可以简化整个程序的易读性。在底下的例子当中,我们以 thanks.c
这个主程序去呼叫 thanks_2.c
这个子程序,写法很简单:
撰写所需要的主、子程序
编写主程序 thanks.c
:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
thanks_2();
}
编写子程序 thanks_2.c
:
#include <stdio.h>
void thanks_2(void)
{
printf("Thank you!\n");
}
进行程序的编译与链接(Link)
开始将源码文件编译成可执行的 binary file:
[root@localhost test1]# gcc -c thanks.c thanks_2.c
[root@localhost test1]# ll
总用量 16
-rw-r--r--. 1 root root 68 6月 5 2009 thanks_2.c
-rw-r--r--. 1 root root 1504 10月 24 16:08 thanks_2.o
-rw-r--r--. 1 root root 77 4月 8 2004 thanks.c
-rw-r--r--. 1 root root 1560 10月 24 16:08 thanks.o
[root@localhost test1]# gcc -o thanks.out thanks.
thanks.c thanks.o
[root@localhost test1]# gcc -o thanks.out thanks.o thanks_2.o
[root@localhost test1]# ll
总用量 28
-rw-r--r--. 1 root root 68 6月 5 2009 thanks_2.c
-rw-r--r--. 1 root root 1504 10月 24 16:08 thanks_2.o
-rw-r--r--. 1 root root 77 4月 8 2004 thanks.c
-rw-r--r--. 1 root root 1560 10月 24 16:08 thanks.o
-rwxr-xr-x. 1 root root 8512 10月 24 16:09 thanks.out
执行生成的可执行文件 thanks.out
:
[root@localhost test1]# ./thanks.out
Hello World
Thank you!
知道为什么要制作出目标文件了吗?由于我们的源代码文件有时并非仅只有一个,所以我们无法直接进行编译。这个时候就需要先产生目标文件,然后再以链接制作成为 binary 可执行文件。另外,如果有一天,你更新了 thanks_ 2.c
这个文件的内容,则你只要重新编译 thanks_ _2.c
来产生新的 thanks_2.o
,然后再以链接制作出新的 binary 可执行文件即可!而不必重新编译其他没有更动过的原始码文件。这对于软件开发者来说,是一个很重要的功能,因为有时候要将偌大的原始码全部编译完成,会花很长的一段时间呢!
此外,如果你想要让程序在执行的时候具有比较好的效能,或者是其他的除错功能时,可以在编译
的过程里面加入适当的参数,例如底下的例子: .
调用外部函式库:加入链接的函式库
刚刚我们都仅只是在屏幕上面印出一些字眼而已,如果说要计算数学公式呢?例如我们想要计算出三角函数里面的 sin(90 度角)。要注意的是,大多数的程序语言都是使用径度而不是一般我们在计算的角度,180度角约等于 3.14 径度。那我们就来写一下这个程序吧!
编写 sin.c
文件:
#include <stdio.h>
#include <math.h>
int main(void)
{
float value;
value = sin ( 3.14 / 2 );
printf("%f\n",value);
}
那要如何编译这支程序呢?我们先直接编译看看:
[root@localhost test2]# gcc sin.c
新版的 GCC 会主动帮你将所需要的函式库抓进来编译,所以不会出现怪异的错误信息!事实上,数学函式库使用的是 libm.so
这个函式库,你最好在编译的时候将这个函式库纳进去比较好~另外要注意,这个函式库放置的地方是系统默认会去找的 /lib
、/lib64
,所以你无须使用底下的 -L
去加入搜寻的目录。而 libm.so
在编译的写法上,使用的是 -lm
(lib
简写为 l
)因此就变成:
编译时加入额外函式库链接的方式
[root@localhost test2]# gcc sin.c -lm -L/1ib -L/1ib64
[root@localhost test2]# ./a.out
1.000000
所以 -lm
表示使用 libm.so
(或 libm.a
)这个函式库的意思,至于那个 -L
后面接的路径呢?这表示要的函式库 libm.so
请到 /lib
或 /lib64
里面搜寻。
上面的说明很清楚了吧,不过要注意的是,由于 Linux 默认是将函式库放置在 /lib
与 /lib64
当中,所以你没有写 -L/lib
与 -L/lib64
也没有关系的。不过,万一哪天你使用的函式库并非放置在这两个目录下,那么 -L/path
就很重要了!否则会找不到函式库。
你或许已经发现一个奇怪的地方,那就是在我们的 sin.c
当中第一行 #include <stdio.h>
,这行说的是要将一些定义数据由 stdio.h
这个文件读入,这些数据包括 printf
的相关设定。这个文件其实是放置在 /usr/include/stdio.h
的,那么万一这个文件并非放置在这里呢?那么我们就可以使用下面的方式来定义出要读取的 include
文件放置的目录:
gcc sin.c -lm -I/usr/include
-Ipath
后面接的路径( Path
)就是设定要去搜寻相关的 include 文件的目录啦!不过,同样的,默认值是放置在 /usr/include
底下,除非你的 include 文件放置在其他路径,否则也可以略过这个环节。
评论区