准备工作
注意:本文可能会让你失望,如果你有下列疑问的话:为什么要在终端输命令啊? GCC 是什么东西,怎么在菜单中找不到? GCC 不能有像 VC 那样的窗口吗?…… 那么你真正想要了解的可能是 anjuta,kdevelop,geany,code blocks,eclipse,netbeans 等 IDE 集成开发环境。即使在这种情况下,由于 GCC 是以上 IDE 的后台的编译器,本文仍值得你稍作了解。
如果你还没装编译环境或自己不确定装没装,不妨先执行
sudo apt-get install gcc
sudo apt-get install build-essential
如果你需要编译 Fortran 程序,那么还需要安装 gfortran(或 g77)
sudo apt-get install gfortran
编译简单的 C 程序
C 语言经典的入门例子是 Hello World,下面是一示例代码:
#include <stdio.h>
int main(void)
{
printf("Hello, world!/n");return 0;
}
我们假定该代码存为文件‘hello.c’。
要用 gcc 编译该文件,
进入hell.c所在的目录
使用下面的命令:
$ gcc -g -Wall hello.c -o hello
该命令将文件‘hello.c’中的代码编译为机器码并存储在可执行文件 ‘hello’中。
机器码的文件名是通过 -o 选项指定的。该选项通常作为命令行中的最后一个参数。如果被省略,输出文件默认为 ‘a.out’。
注意到如果当前目录中与可执行文件重名的文件已经存在,它将被覆盖。
选项 -Wall 开启编译器几乎所有常用的警告──强烈建议你始终使用该选项。编译器有很多其他的警告选项,但 -Wall是最常用的。默认情况下GCC 不会产生任何警告信息。当编写 C 或 C++ 程序时编译器警告非常有助于检测程序存在的问题。 注意如果有用到math.h库等非gcc默认调用的标准库,请使用-lm参数
本例中,编译器使用了 -Wall 选项而没产生任何警告,因为示例程序是完全合法的。
选项 “”-g”" 表示在生成的目标文件中带调试信息,调试信息可以在程序异常中止产生core后,帮助分析错误产生的源头,包括产生错误的文件名和行号等非常多有用的信息。
要运行该程序,输入可执行文件的路径如下:
$ ./hello
Hello, world!
这将可执行文件载入内存,并使 CPU 开始执行其包含的指令。 路径 ./ 指代当前目录,因此 ./hello 载入并执行当前目录下的可执行文件 ‘hello’。
编译多个源文件
CFLAGS=-Wall
hello: hello.o hello_fn.o
clean:
rm -f hello hello.o hello_fn.o
为便于不熟悉 make 的读者理解,本节提供一个简单的用法示例。Make 凭借本身的优势,可在所有的 Unix 系统中被找到。要了解关于Gnu make 的更多信息,请参考 Richard M. Stallman 和 Roland McGrath 编写的 GNU Make 手册。
Make 从 makefile(默认是当前目录下的名为‘Makefile’的文件)中读取项目的描述。makefile指定了一系列目标(比如可执行文件)和依赖(比如对象文件和源文件)的编译规则,其格式如下:
目标: 依赖
命令
对每一个目标,make 检查其对应的依赖文件修改时间来确定该目标是否需要利用对应的命令重新建立。注意到,makefile 中命令行必须以单个的 TAB 字符进行缩进,不能是空格。
GNU Make 包含许多默认的规则(参考隐含规则)来简化 makefile 的构建。比如说,它们指定‘.o’文件可以通过编译‘.c’文件得到,可执行文件可以通过将‘.o’链接到一起获得。隐含规则通过被叫做make变量的东西所指定,比如CC(C 语言编译器)和 CFLAGS(C程序的编译选项);在makefile文件中它们通过独占一行的 变量=值 的形式被设置。对C++ ,其等价的变量是CXX和CXXFLAGS,而变量CPPFLAGS则是编译预处理选项。
现在我们为上一节的项目写一个简单的 makefile 文件:
CC=gcc
CFLAGS=-Wall
hello: hello.o hello_fn.o
clean:
rm -f hello hello.o hello_fn.o
该文件可以这样来读:使用 C 语言编译器 gcc,和编译选项‘-Wall’,从对象文件‘hello.o’和‘hello_fn.o’生成目标可执行文件 hello(文件‘hello.o’和‘hello_fn.o’通过隐含规则分别由‘hello.c’和‘hello_fn.c’生成)。目标clean没有依赖文件,它只是简单地移除所有编译生成的文件。rm命令的选项 ‘-f’(force) 抑制文件不存在时产生的错误消息。
另外,需要注意的是,如果包含main函数的cpp文件为A.cpp, makefile中最好把可执行文件名也写成 A。
要使用该 makefile 文件,输入 make。不加参数调用make时,makefile文件中的第一个目标被建立,从而生成可执行文件‘hello’:
$ make
gcc -Wall -c -o hello.o hello.c
gcc -Wall -c -o hello_fn.o hello_fn.c
gcc hello.o hello_fn.o -o hello
$ ./hello
Hello, world!
一个源文件被修改要重新生成可执行文件,简单地再次输入 make 即可。通过检查目标文件和依赖文件的时间戳,程序 make 可识别哪些文件已经修改并依据对应的规则更新其对应的目标文件:
$ vim hello.c (打开编辑器修改一下文件)
$ make
gcc -Wall -c -o hello.o hello.c
gcc hello.o hello_fn.o -o hello
$ ./hello
Hello, world!
最后,我们移除 make 生成的文件,输入 make clean:
$ make clean
rm -f hello hello.o hello_fn.o
一个专业的 makefile文件通常包含用于安装(make install)和测试(make check)等额外的目标。
本文中涉及到的例子都足够简单以至于可以完全不需要makefile,但是对任何大些的程序都使用 make 是很有必要的。
链接外部库
库是预编译的目标文件(object files)的集合,它们可被链接进程序。静态库以后缀为‘.a’的特殊的存档文件(archive file)存储。
标准系统库可在目录 /usr/lib 与 /lib 中找到。比如,在类 Unix 系统中 C 语言的数学库一般存储为文件/usr/lib/libm.a。该库中函数的原型声明在头文件 /usr/include/math.h 中。C 标准库本身存储为/usr/lib/libc.a,它包含 ANSI/ISO C 标准指定的函数,比如‘printf’。对每一个 C 程序来说,libc.a 都默认被链接。
下面的是一个调用数学库 libm.a 中 sin 函数的的例子,创建文件calc.c:
#include <math.h>
#include <stdio.h>int main (void)
{
double x = 2.0;
double y = sin (x);
printf ("The value of sin(2.0) is %fn", y);return 0;
}
尝试单独从该文件生成一个可执行文件将导致一个链接阶段的错误:
$ gcc -Wall calc.c -o calc
/tmp/ccbR6Ojm.o: In function ‘main’:
/tmp/ccbR6Ojm.o(.text+0×19): undefined reference to ‘sin’
函数 sin,未在本程序中定义也不在默认库‘libc.a’中;除非被指定,编译器也不会链接‘libm.a’。
为使编译器能将 sin 链接进主程序‘calc.c’,我们需要提供数学库‘libm.a’。一个容易想到但比较麻烦的做法是在命令行中显式地指定它:
$ gcc -Wall calc.c /usr/lib/libm.a -o calc
函数库‘libm.a’包含所有数学函数的目标文件,比如sin,cos,exp,log及sqrt。链接器将搜索所有文件来找到包含 sin的目标文件。
一旦包含 sin 的目标文件被找到,主程序就能被链接,一个完整的可执行文件就可生成了:
$ ./calc
The value of sin(2.0) is 0.909297
可执行文件包含主程序的机器码以及函数库‘libm.a’中 sin 对应的机器码。
为避免在命令行中指定长长的路径,编译器为链接函数库提供了快捷的选项‘-l’。例如,下面的命令
$ gcc -Wall calc.c -lm -o calc
与我们上面指定库全路径‘/usr/lib/libm.a’的命令等价。
一般来说,选项 -lNAME使链接器尝试链接系统库目录中的函数库文件 libNAME.a。一个大型的程序通常要使用很多 -l选项来指定要链接的数学库,图形库,网络库等。