# 使用传统程序语言进行编译的简单范例

通过上面的介绍之后,差不多能清楚一点概念了,本章以一个简单的程序范例来说明整个编译的过程

# 单一程序:打印 Hello World

TIP

请确保你的 Linux 已经安装了 gcc,如果没有安装先参考后续章节的 RPM 或则 yum 方式安装

# 编写程序代码

[root@study ~]# vim hello.c
#include <stdio.h>

int main(void){
 printf("Hello word\n");
}
1
2
3
4
5
6

上面是用 C 语言的语法写成的一个程序文件,第一行的是声明信息

# 开始编译与测试执行

[root@study ~]# gcc hello.c 
[root@study ~]# ll hello.c a.out 
-rwxr-xr-x. 1 root root 8360 Apr  5 16:24 a.out		# 产生这个文件名
-rw-r--r--. 1 root root   63 Apr  5 16:24 hello.c

[root@study ~]#./a.out 
Hello word
# 执行成功了
1
2
3
4
5
6
7
8

在默认情况下,以 gcc 编译源码,没有加任何参数的情况下,执行文件的名称会以 a.out 名称出现。a.out 就是编译成功的可执行的 binary program

如果想要产生目标文件(object file)来进行其他的动作,而且自定义执行文件名

[root@study ~]# gcc -c hello.c 
[root@study ~]# ll hello*
-rw-r--r--. 1 root root   63 Apr  5 16:24 hello.c
-rw-r--r--. 1 root root 1496 Apr  5 16:27 hello.o  # 产生的目标文件

[root@study ~]# ll hello*
-rwxr-xr-x. 1 root root 8360 Apr  5 16:28 hello		# 自定义文件名的可执行文件 -o 
-rw-r--r--. 1 root root   63 Apr  5 16:24 hello.c
-rw-r--r--. 1 root root 1496 Apr  5 16:27 hello.o

[root@study ~]./hello 
Hello word
1
2
3
4
5
6
7
8
9
10
11
12

# 主、子程序连接:子程序的编译

如果我们在一个主程序里面又调用了另一个字程序呢?下面的例子中以 thans.c 这个主程序去调用 thanks_2.c 这个子程序

# 编写主、子程序代码

# 1. 编写主程序
[root@study ~]# vim thanks.c
#include <stdio.h>

int main(void){
 printf("Hello word\n");
 thanks_2();
}

# 2. 编写子程序
[root@study ~]# vim thanks_2.c
#include <stdio.h>

int thanks_2(void){
 printf("Thank you!\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2. 将源码编译为颗执行的 binary file
[root@study ~]# gcc -c thanks.c thanks_2.c 
[root@study ~]# ll thanks*
-rw-r--r--. 1 root root   76 Apr  5 16:33 thanks.c
-rw-r--r--. 1 root root 1560 Apr  5 16:35 thanks.o
-rw-r--r--. 1 root root   67 Apr  5 16:34 thanks_2.c
-rw-r--r--. 1 root root 1504 Apr  5 16:35 thanks_2.o
# *.o 文件是编译产生的

[root@study ~]# gcc -o thanks thanks.o thanks_2.o 
[root@study ~]# ll thanks*
-rwxr-xr-x. 1 root root 8424 Apr  5 16:36 thanks
-rw-r--r--. 1 root root   76 Apr  5 16:33 thanks.c
-rw-r--r--. 1 root root 1560 Apr  5 16:35 thanks.o
-rw-r--r--. 1 root root   67 Apr  5 16:34 thanks_2.c
-rw-r--r--. 1 root root 1504 Apr  5 16:35 thanks_2.o

# 3. 执行
[root@study ~]# ./thanks 
Hello word
Thank you!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这里你就明白了,为什么要先制作出目标文件了,由于我们的源码文件有时并非只有一个文件,所以无法直接进行编译。这个时候就需要先产生目标文件,再以连结制作为 binary 可执行文件

另外,如果有一天,你更新了 thanks_2.c 这个文件的内容,则你只需要重新编译 thanks_2.c 来产生新的 thanks_2.o ,然后再以连结制作出新的 binary 可执行文件即可。这功能对于庞大的软件功能源码来说会很有用,节省很多的时间

此外,想要程序再执行的时候有比较好的效率,或则是其他的除错功能时,可以在编译的过程里加入适当的参数,如

# -O 产生优化的参数
[root@study ~]# gcc -O -c thanks.c thanks_2.c

# -Wall 产生更详细的编译过程信息
[root@study ~]# gcc -Wall -c thanks.c thanks_2.c
thanks.c: In function 'main':
thanks.c:5:2: warning: implicit declaration of function 'thanks_2' [-Wimplicit-function-declaration]
  thanks_2();
  ^
thanks.c:6:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^
thanks_2.c: In function 'thanks_2':
thanks_2.c:5:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^
# 上面的信息为 warning 信息,所以可以忽略也没有关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

至于更多的 gcc 额外参数功能,请自行 man gcc

# 调用外部函数库:加入连结的函数库

比如要计算数学公式,计算三角函数里面的 sin(90 度角)。要注意的是,大多数程序语言都是使用径度,而不是一般我们计算的角度,180 度角约等于 3.14 径度

[root@study ~]# vim sin.c
# include <stdio.h>
# include <math.h>

int main(void){
	float value;
	value = sin(3.14 / 2);
	printf("%f\n",value);
}

# 编译
[root@study ~]# gcc sin.c 
1
2
3
4
5
6
7
8
9
10
11
12

新版的 GCC 会主动将你所需要的函数库抓进来编译,所以不会出现错误信息,事实上数据函数库使用的是 libm.so 这个函数库,最好在编译的时候将整个函数库链接进来比较好。

函数库放置的地方是系统默认会去找 /lib/lib64 ,所以无须使用 -L 加入搜索的目录,而 libm.so 在编译的写法上,使用的是 -lm (lib 简写为 l)

# 编译时加入额外的函数库连结方式

[root@study ~]# gcc sin.c -lm -L/lib64
# 重点在 -lm,后面的 -L 是指定搜索的目录
[root@study ~]#./a.out 
1.000000
1
2
3
4

-lm 是有意义的,可以拆开成两部分来看:

  • -l:加入某个函数库(library)
  • m:libm.so 这个函数库,其中 lib 与扩展名 (.a 或 .so )不需要写

对于第一行的 stdio.sh 则是放在 /usr/include/stdio.sh ,如果不在这里的位置,那么久需要指明在哪里

gcc sin.c -lm -I/usr/include
# include 的路径指明格式为:-I/path
1
2

通过以上范例,对 gcc 与源码有一定程度的认识了,下面整理下 gcc 的简易使用方法

# gcc 的简易用法(编译、参数与连结)

下面列举几个常用的 gcc 常见的参数用法(详情请 man gcc)

# 仅将源码编译为目标文件,并不制作连结等功能
gcc -c hello.c
# 会自动产生 hello.o 文件,但是并不会产生 binary 执行文件

# 在编译时,根据作业环境给予优化执行速度
gcc -O hello.c -c
# 会自动产生 hello.o 文件,并进行优化

# 在进行 binary file 制作时,将连结的函数库与相关的路径填入
gcc sin.c -lm -L/lib -I/usr/include
# 该指令常下达在最终连结成 binary file 的时候
# -lm 指 libm.so 或 libm.a 这个函数库文件
# -L 后面接的路径是刚刚那个函数库的搜索目录
# -I 后面接的是源码内的 include 文件所在目录

# 将编译的结果输出到某个特定的文件
gcc -o hello hello.c
# -o 后面接的是要输出的 binary file 文件名

# 在编译的时候,输出较多的信息说明
gcc -o hello hello.c -Wall
# -Wall,程序的编译会变得较为严谨一点,所以警告信息也会显示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

比较常用的如上,另外,通常称 -Wall-O 这些非必要的参数为旗标 (FLAGS),因为使用的是 C 语言,所以有时候也会简称这些旗标为 CFLAGS,这些变量偶尔会被使用。

在后面的 make 相关用法时,用得较多