目录
一、gcc 简易入门
1. gcc 简介
- 什么是 gcc,它能干什么?
GCC(GNU Compiler Collection) 即 GNU 编译器套件,属于一种编程语言编译器,其原名为 GCC(GNU C Compiler)即 GNU c 语言编译器,虽然缩写一样但是功能上区别很大。GCC 的初衷是为 GNU 操作系统专门编写的一款编译器,原本的 GNU 是专用于编译 C 代码,现如今已扩展为可以编译 C、C++、Java、Objective-C 等多种编程语言的编译器集合了。这篇文章主要介绍 gcc 或 g++ 的使用。
- GCC、gcc、g++三者有何关系?
gcc(GUN C Compiler)是 GCC 中的 c 编译器,而 g++(GUN C++ Compiler)是 GCC 中的 c++ 编译器。
gcc 和 g++ 两者都可以编译 c 和 cpp 文件,但存在差异。gcc 在编译 cpp 时语法按照 c 来编译但默认不能链接到 c++ 的库(gcc 默认链接 c 库,g++ 默认链接 c++ 库)。g++ 编译 .c 和 .cpp 文件都统一按 cpp 的语法规则来编译。所以一般编译 c 用 gcc,编译 c++ 用 g++。
2. gcc 编译过程
$ gcc -o hello hello.c
上面指令可以通过 gcc 将 c 源文件 hello.c 直接生成可执行程序 hello。这条命令隐含执行了 【 预处理、汇编、编译、链接形成最终的二进制可执行程序】 的过程。
在使用GCC编译程序时,编译过程可以被细分为四个阶段,包括【预处理、编译、汇编、链接】。
-
预处理(pre-processing)-E
在预处理阶段,编译器主要做:①加载头文件 ②宏替换 ③条件编译 ④产生后缀名为 [ .i ] 的文本文件。
带 "#" 的语句一般都是在预处理阶段处理。我们可以通过 gcc 的 -E 选项进行查看,如下所示:
gcc -E hello.c -o hello.i
编译器将 hello.c 预处理结果输出 hello.i 文件。
在编译过程中,编译器主要做:①语法检查和词法语义分析。 ②在确认所有指令都符合语法规则之后,将其翻译成等价的中间代码或者是汇编代码。③产生后缀名为 [ .s ] 的汇编文本文件。
gcc -S hello.i -o hello.s
编译器将预处理结果文件 hello.i 翻译成汇编代码 hello.s
汇编阶段中,编译器主要 ①将汇编语言代码翻译成机器指令 ②产生后缀名为[ .o ]的二进制目标文件。也就是是把编译阶段生成的 ".s" 文件转成二进制目标代码(obj)。
gcc -c hello.s -o hello.o
编译器将 hello.s 文件转化为 hello.o 文件。
在成功编译之后,就进入了链接阶段。链接就是将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。
编译器主要做:①进行动态链接或静态链接 ②产生可二进制执行文件。
gcc hello.o -o hello
编译器将 hello.o 链接成最终可执行文件 hello。
3. gcc 命令的常用选项
选项 | 解释 |
---|---|
-ansi | 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 |
-c | 只编译并生成目标文件。 |
-DMACRO | 以字符串"1"定义 MACRO 宏。 |
-DMACRO=DEFN | 以字符串"DEFN"定义 MACRO 宏。 |
-E | 只运行 C 预编译器。 |
-g | 生成调试信息。GNU 调试器可利用该信息。可以方便 gdb 调试。 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY。 |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY。 |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY。 |
-m486 | 针对 486 进行代码优化。 |
-o FILE | 生成指定的输出文件。用在生成可执行文件时。 |
-O0 | 不进行优化处理。 |
-O 或 -O1 | 优化生成代码。 |
-O2 | 进一步优化。 |
-O3 | 比 -O2 更进一步优化,包括 inline 函数。 |
-shared | 生成共享目标文件。通常用在建立共享库时。 |
-static | 禁止使用共享连接。 |
-UMACRO | 取消对 MACRO 宏的定义。 |
-w | 不生成任何警告信息。 |
-Wall | 生成所有警告信息。 |
4. gcc 编译单文件依赖目标
//hello.c
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
要用 gcc 编译该文件,使用下面的命令:
$ gcc -g -Wall hello.c -o hello
a. 该命令将文件 hello.c 中的代码编译为机器码并存储在可执行文件 hello 中。机器码的文件名是通过 -o 选项指定的。该选项通常作为命令行中的最后一个参数。如果被省略,输出文件默认为 ‘a.out’。如果当前目录中与可执行文件重名的文件已经存在,它将被复盖。
b. 选项 -Wall 开启编译器几乎所有常用的警告──强烈建议你始终使用该选项。编译器有很多其他的警告选项,但 -Wall 是最常用的。默认情况下 GCC 不会产生任何警告信息。当编写 C 或 C++ 程序时编译器警告非常有助于检测程序存在的问题。
c. 选项 -g 表示在生成的目标文件中带调试信息,调试信息可以在程序异常中止产生 core 后,帮助分析错误产生的源头,包括产生错误的文件名和行号等非常多有用的信息。
$ ./hello
Hello, world!
5. gcc 编译多文件依赖目标
下面的例子中我们将程序 Hello World 分割成 3 个文件:hello.c,hello_fn.c 和头文件 hello.h。
//hello.c
#include "hello.h"
int main(void)
{
hello ("world");
return 0;
}
//hello.h
void hello (const char * name);
//hello_fn.c
#include <stdio.h>
#include "hello.h"
void hello (const char * name)
{
printf ("Hello, %s!\n", name);
}
PS: 语句 #include "FILE.h" 与 #include <FILE.h> 有所不同:前者在搜索系统头文件目录之前将先在当前目录中搜索文件‘FILE.h’,后者只搜索系统头文件而不查看当前目录。
要用gcc编译以上源文件,使用下面的命令:
$ gcc -Wall hello.c hello_fn.c -o newhello
注意到头文件 hello.h 并未在命令行中指定。源文件中的 #include "hello.h" 指示符使得编译器自动将其包含到合适的位置。
$ ./newhello
Hello, world!
二、使用 Makefile
为便于不熟悉 make 的读者理解,本节提供一个简单的用法示例。Make 凭借本身的优势,可在所有的 Unix 系统中被找到。要了解关于Gnu make 的更多信息,请参考 Richard M. Stallman 和 Roland McGrath 编写的 GNU Make 手册。
Make 从 makefile(默认是当前目录下的名为‘Makefile’的文件)中读取项目的描述。makefile指定了一系列目标(比如可执行文件)和依赖(比如对象文件和源文件)的编译规则,其格式如下:
target : dependencies
<TAB>command----------------------------------------------
目标 : 依赖
<TAB>命令
对每一个目标,make 检查其对应的依赖文件修改时间来确定该目标是否需要利用对应的命令重新建立。注意到,makefile 中命令 行必须以单个的 TAB 字符进行缩进,不能是空格。
GNU Make 包含许多默认的规则(隐含规则)来简化 makefile 的构建。比如说,它们指定 '.o' 文件可以通过编译 '.c' 文件得到,可执行文件可以通过将 '.o' 链接到一起获得。隐含规则通过被叫做make变量的东西所指定,比如 CC(C 语言编译器)和 CFLAGS(C程序的编译选项);在 makefile 文件中它们通过独占一行的 变量=值 的形式被设置。对 C++ ,其等价的变量是 CXX 和 CXXFLAGS,而变量 CPPFLAGS 则是编译预处理选项。更多细节可以参考 makefile 的详细教程。
使用简单的 makefile 实现上述编译
现在我们为上一节的项目写一个简单的 makefile 文件:
//makefile
CC=gcc
CFLAGS=-Wall
# 目标 hello 依赖于 hello.o 和 hello_fn.o
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 =》修改 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 是很有必要的。
三、链接外部库
1. gcc 连接外部库
库是预编译的目标文件(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:
//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 %f\n", y);
return 0;
}
尝试单独从该文件生成一个可执行文件将导致一个链接阶段的错误:
$ gcc -Wall calc.c -o calc
/tmp/cclYCmfu.o: In function `main':
calc.c:(.text+0x24): undefined reference to `sin'
collect2: error: ld returned 1 exit status
函数 sin,未在本程序中定义也不在默认库‘libc.a’中;除非被指定,编译器也不会链接‘libm.a’。
为使编译器能将 sin 链接进主程序‘calc.c’,我们需要提供数学库‘libm.a’。一个容易想到但比较麻烦的做法是在命令行中显式地指定它:
$ gcc -Wall calc.c /usr/lib/x86_64-linux-gnu/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'。例如,下面的命令与我们上面指定库全路径 '/usr/lib/x86_64-linux-gnu/libm.a' 的命令等价。
$ gcc -Wall calc.c -lm -o calc
一般来说,选项 -lNAME使链接器尝试链接系统库目录中的函数库文件 libNAME.a。一个大型的程序通常要使用很多 -l选项来指定要链接的数学库,图形库,网络库等。
2. g++连接外部库
GCC 是 GNU 编译器集合(GNU Compiler Collection)的首字母缩写词。GNU 编译器集合包含 C,C++,Objective-C,Fortran,Java 和 Ada 的前端以及这些语言对应的库(libstdc++,libgcj,……)。前面我们只涉及到 C 语言,那么如何用 gcc 编译其他语言呢?本节将简单介绍 C++ 编译的例子。首先我们尝试编译简单的 C++ 的经典程序 Hello world:
//hello.cpp
#include <iostream>
int main(int argc,char *argv[])
{
std::cout << "hello, world" << std::endl;
return 0;
}
将文件保存为‘hello.cpp’,用 gcc 编译,结果如下:
$ gcc -Wall hello.cpp -o hello
/tmp/cch6oUy9.o: In function__static_initialization_and_destruction_0(int, int)': hello.cpp:(.text+0x23): undefined reference to
std::ios_base::Init::Init()'
/tmp/cch6oUy9.o: In function__tcf_0': hello.cpp:(.text+0x6c): undefined reference to
std::ios_base::Init::~Init()'
/tmp/cch6oUy9.o: In functionmain': hello.cpp:(.text+0x8e): undefined reference to
std::cout'
hello.cpp:(.text+0x93): undefined reference tostd::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)' /tmp/cch6oUy9.o:(.eh_frame+0x11): undefined reference to
__gxx_personality_v0'
collect2: ld returned 1 exit status
出错了!!而且错误还很多,很难看懂,这可怎么办呢?在解释之前,我们先试试下面的命令:
$ gcc -Wall hello.cpp -o hello -lstdc++
噫,加上-lstdc++选项后,编译竟然通过了,而且没有任何警告。运行程序,结果如下:
$ ./hello
hello, world
通过上节,我们可以知道,-lstdc++ 选项用来通知链接器链接静态库 libstdc++.a。而从字面上可以看出,libstdc++.a 是C++ 的标准库,这样一来,上面的问题我们就不难理解了──编译 C++ 程序,需要链接 C++ 的函数库 libstdc++.a。
编译 C 的时候我们不需要指定 C 的函数库,为什么 C++ 要指定呢?这是由于早期 gcc 是指 GNU 的 C 语言编译器(GNU C Compiler),随着 C++,Fortran 等语言的加入,gcc的含义才变化成了 GNU 编译器集合(GNU Compiler Collection)。C作为 gcc 的原生语言,故编译时不需额外的选项。
不过幸运的是,GCC 包含专门为 C++ 、Fortran 等语言的编译器前端。于是,上面的例子,我们可以直接用如下命令编译:
$ g++ -Wall hello.cpp -o hello
参考:
gcc 使用入门教程:https://www.cnblogs.com/postw/archive/2004/01/13/9670805.html
GCC 参数详解:https://www.runoob.com/w3cnote/gcc-parameter-detail.html
gcc -c与gcc -o以及不加参数的区别:https://blog.csdn.net/BobYuan888/article/details/88709449