GCC 全称"GNU C Compiler",不过自从面世后,增加了多种语言的支持,不过用的最多的是还是编译C或C++程序(另外有个工具叫做G++)。GCC是一种多目标编译器,通过可交互的后端处理器,为多种计算机架构生成可执行程序。
话说回来,什么是编译器呢?
编译器并不是一个单一的程序,它们通常由六七个稍小的程序组成,这些程序由一个叫做“编译器驱动器(Compiler driver)"的控制程序调用。
编译器一般由以下几部分组成:
- 预处理器(preprocessor)
- 语法和语义检查器(syntactic and semantic checker)
- 代码生成器(code generator)
- 汇编程序(assembler)
- 优化器(optimizer)
- 链接器(linker)
链接器确认main函数的初始进入点(程序开始执行的地方),把符号引用(symbolic reference)绑定到内存地址,把所有的目标文件集中到一起,再加上库文件,从而产生可执行文件。
本文主要介绍使用GCC编译C程序,不过GCC不支持C语言的许多“方言”,目前一般使用命令行参数 -std=c99 指定编译器支持C99标准。GCC对C11的标准支持是不完整的,尤其是涉及定义在头文件<threads.h>中的多线程函数。这是因为GCC的C链接库长期以来支持POSIX标准的多线程功能,与C11中的多线程功能非常相似。
一般gcc程序会链接到cc上:
$ ls -alF /usr/bin/cc
lrwxrwxrwx 1 root root 20 1月 4 09:20 /usr/bin/cc -> /etc/alternatives/cc*$ ls -alF /etc/alternatives/cclrwxrwxrwx 1 root root 12 1月 4 09:20 /etc/alternatives/cc -> /usr/bin/gcc*
可以通过--version选项查看gcc版本:
$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
一、 逐步实现使用gcc编译c程序
下面介绍gcc命令各个选项,控制编译过程的各个阶段:预处理(preprocessing)、编译(compiler)、汇编(assembling)和链接(linking)。也可以调用独立的工具,如c语言的预处理器cpp、汇编器as和链接器ld,来独立执行对应的步骤。
1.1 预处理
以下面代码为例:
#include <stdio.h>
int main(int argc, char *argv[])
{
/*print hello world*/
printf("hello world!\n");
return 0;
}
将源代码交给编译器之前,由预处理器执行命令,展开源代码中的宏。-E 选项指示gcc在预处理器完毕后即可停止。预处理器的输出会被导入到标准输出流,也可以通过 -o 选项把它输出到文件:
$ gcc -E -o helloworld.i helloworld.c
$ cat helloworld.i
...
# 3 "helloworld.c"
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0;
}
一般来说,预编译器会将源代码中的注释去掉,可以使用 -C 选项阻止预编译器删除注释。
$ gcc -E -C -o helloworld.i helloworld.c
$ cat helloworld.i
...
# 3 "helloworld.c"
int main(int argc, char *argv[])
{
/*print hello world*/
printf("hello world!\n");
return 0;
}
关于gcc预处理器的常用选项:
选项 | 含义 |
---|---|
-Dname[=definition] | 配合 #ifdef name 实现条件编译,这里的name必须是源代码中没有#define过的;若没有指定definition,则该name被定义为1. |
-Uname | 若源代码中define过符号name,则“取消”该符号的定义,-U和-D会依据在命令中的先后顺序进行处理。 |
-Idirectory[: directory[...]] | 指定系统标准include目录外的头文件搜索目录。 |
-iquote directory[:directory[...]] | 仅指定include中采用引号而非尖括号的头文件搜索目录。 |
-isystem directory[:directory[...]] | 优先于系统include目录的搜索目录。在目录开头位置的等号,被视作系统根目录的占位符,可以使用--sysroot或-isysroot选项来修改它。 |
-isysroot directory 和 --sysroot directory | -isysroot指定包含目录的系统根目录,--sysroot指定链接库的系统根目录。例如,编译器指定包含目录为/usr/include及其子目录,则该选项将引导到directory/usr/include下进行搜索。 |
-I- | 新版gcc中被-iquote代替。-I-左边加上-I目录,只对#include中引号包含的头文件进行搜索。-I-右边加上-I目录,对#include中引号和尖括号的头文件进行搜索。而且,如果命令中出现了-I-,则源文件本身的目录不再被搜索。 |
对于头文件的搜索目录,通常的顺序是:
- 包含指定的源文件目录(#include引号包含的文件名);
- 采用-iquote选项指定的目录;
- -I选项指定的目录;
- 采用环境变量CPATH指定的目录;
- 采用-isystem指定的目录;
- 采用环境变量C_INCLUDE_PATH指定的目录;
- 系统默认include目录。
1.2 编译
编译器的核心是把C程序翻译成汇编语言,汇编语言是人可以读懂的语言,也是更接近机器码的语言。各种CPU架构都有不同的汇编语言。
使用-S
选项,让编译程序生成汇编语言输出后立刻停止。如果没有指定输出文件名,那么采用-S
选项的gcc编译过程会为每个编译输出文件生成以.s
作为后缀的汇编语言文件。并且使用-fverbose-asm
选项,将c语言中的变量名称作为汇编语言中的注释。
$ gcc -S -fverbose-asm helloworld.c # 生成helloworld.s
1.3 汇编
每个机器架构都有自己的汇编语言,gcc调用宿主系统的汇编器,将汇编程序翻译成可执行的二进制代码。
该结果是个对象文件(object file),它包含机器码以执行对应的源文件中所定义的函数,它同时包含一个符号表,描述了源文件中具有外部链接的所有对象。
使用-c
选项指示gcc不会链接该程序,但会对每个输入文件生成对象文件,其文件名后缀是.o
:
$ gcc -c helloworld.c # 生成helloworld.o
使用-Wa
选项把命令行选项传递给汇编器:
$ gcc -v -o hello -Wa,-as=hello.sym,-L helloworld.c # 生成hello.sym 和 hello可执行程序
这里的-as=hello.sym
表示在单独一个列表中输出模块的符号表,-L
表示在符号表中包含本地符号表。
1.4 链接
链接器把多个二进制文件链接成一个可执行文件。
标准库的大部分函数通常放在libc.a
文件中,或者放在libc.so
的动态共享链接文件中。这些链接库一般位于/lib
或 /usr/lib
中.
以下列代码为例:
/*helloworld.h*/
void hello();
/*helloworld.c*/
#include "helloworld.h"
void hello() {printf("hello world!\n");}
/*main.c*/
#include "helloworld.h"
void main(int argc, char *argv[]) {hello();}
创建动态链接库和静态链接库
说到链接,先讲一讲动态链接文件和静态链接文件。
如果想创建一个共享对象文件,可以使用gcc的-shared
选项。输入文件必须是一个已存在的对象文件。
$ gcc -c helloworld.c # 生成.o文件
$ gcc -shared -o libhelloworld.so helloworld.o # 生成.so文件
$ ar -rc libhelloworld.a helloworld.o # 生成.a文件
链接
通常,gcc会自动在标准库目录中搜索文件,例如/usr/lib。在-l选项后紧跟库的名字:
$ gcc -o hello main.c -lhelloworld # 若libhelloworld.a在标准库目录下,则自动链接,此处会出错
关于链接搜索路径以外的链接库,有三种方式:
$ gcc -o hello main.c libhelloworld.a # 把链接库作为一般对象文件
$ gcc -o hello main.c -L./ -lhelloworld # 使用-L选项增加库目录的搜索路径
$ gcc -o hello main.c -lhelloworld # 需要将链接库的目录加到环境变量LIBRARYPATH中
关于动态库的链接,可以将.so
文件拷贝到/usr/lib
文件夹下,使用-l
选项链接,也可以直接指定lib*.so
的文件名:
# 先将libhelloworld.so拷贝到/usr/lib目录下
$ gcc -o hello main.c -lhelloworld
$ gcc -o hello main.c libhelloworld.so # 二者选其一
如果系统支持共享链接库,但是不想使用时。可以使用:
$ gcc -static -o hello main.c -lhelloworld
两个重要选项:
- 使用
-save-temps
选项生成所有中间文件,与对应的源文件具有相同的文件名,但文件扩展名分别为.i
、.s
和.o
。 - 使用
-fsyntax-only
选项,就不会执行预处理、编译、汇编和链接。只会测试输入文件的语法是否正确。
二、 编译器警告
gcc可以对其提供的警告信息进行充分控制。
如果不希望区分警告和错误,可以使用-Werror
,让gcc遇到任何警告时都停止编译。
可以通过以-W
开头的选项,来逐个启用大部分gcc警告。例如,当使用switch语句的时候,如果没有对应的default标签,则-Wswitch-default
选项会让gcc产生一个警告信息。-Wall
可以启用大部分的警告。如果使用-Wall
选项,但是希望取消其中的部分警告,可以在单独警告选项中-W
后面插入no-
来指出禁用这部分警告,例如,Wno-switch-default
选项会关闭对于switch语句没有对应default标签的警告。
-Wextra
选项会对合法但有疑问的表达式发出警告,也会对没有副作用和值被丢弃的表达式发出警告。
环境变量
变量 | 含义 |
---|---|
CPATH、C_INCLUDE_PATH | 用逗号分隔的目录列表,以提供头文件的搜索位置。在搜索完命令行-Idirectory 选项所指定的目录后才搜索的目录。 |
COMPILER_PATH | 用逗号隔开的目录列表,以提供gcc自身子程序的搜索位置。 |
GCC_EXEC_PREFIX | 当GCC调用子程序时,需要加在子程序名称前面的前缀。 |
LIBRARY_PATH | 用逗号隔开的目录列表,以提供链接器寻找链接库的位置。在搜索完命令行-Idirectory 选项所指定的目录后才搜索的目录。 |
LD_LIBRARY_PATH | 用逗号隔开的目录列表,以提供共享链接库文件的搜索位置。该变量不是由gcc读取,而是由执行文件读取,并动态链接到共享链接库。 |
TMPDIR | 临时文件使用的目录(一般指定为/tmp) |
参考文献:《C语言核心技术》--Peter Prinz, Tony Crawford
原文地址:https://www.cnblogs.com/addsomesugar/p/12184425.html