GCC简介
GCC即GNU C Compiler,时GNU推出的功能强大、性能优越的多平台编辑器,是GNU的GCC编代表作之一。GCC可以在多种硬件平台上编译出可执行程序,其执行效率与一般的编译器相比平均效率要高20%~30%。GCC编译器能将C、C++语言源程序、汇编程序编译/连接成可执行文件。
二、编译过程
程序在编译时,GCC需要调用预处理程序cpp,由它负责展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容。接着GCC会调用ccl和as将处理后的源代码编译成目标代码。最后GCC会调用链接程序ld把生成的目标代码链接成一个可执行程序。
也就是说使用GCC编译程序时,编译过程可细分为四个阶段:预处理阶段、编译、汇编、链接。
预处理阶段(Pre-Processing)
使用-E参数可以让GCC在预处理结束后停止编译过程
gcc -E hello.c -o hello.i
编译(Compiling)
接下来是代码的编译阶段,在这个阶段中GCC首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要作的工作。在检查无误后GCC把代码翻译成汇编语言。用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。汇编语言是非常有用的,它为不同高级语言不同编译器提供了通用的语言。
使用指令gcc -S hello.i -o hello.s执行编译过程。打开生成的hello.s文件如下图所示
文件类型
GCC通过后缀来区别输入文件的类别:
.c为后缀的文件:C语言源代码文件
.a为后缀的文件:是由目标文件构成的库文件
.C、.cc或.cxx为后缀的文件:C++源代码文件
.h为后缀的文件:头文件
.i为后缀的文件:已经预处理过的C源代码文件
.ii为后缀的文件:已经预处理的C++源代码文件
.o为后缀的文件:编译后的目标文件
.s为后缀的文件:汇编语言源代码文件
.S为后缀的文件:经过预编译的汇编语言源代码文件
编译选项
GCC最基本的用法是:
gcc [options] [filenames]
options:编译器所需要的编译选项。
filenames:要编译的文件名。
关于编译选项,GCC编译器的编译选项大约有100多个,其中多数选项基本用不到,这里只介绍其中最基本、最常用的参数。
-o output_filename:确定可执行文件的名称为output_filename。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
-c:只编译,不链接成可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件。
-g:产生调试工具(GNU的gdb)所必要的符号信息,如果需要对编译的程序进行调试就必须加上这个选项。
-O:对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、链接过程中进行优化处理。这样产生的可执行文件的执行效率可以提高,但编译、链接的速度就相应的要慢一些。
-O2:比-O更好的优化编译、链接,当然整个编译、链接过程会非常慢。
-l dirname:将dirname所指出的目录加入到程序头文件目录列表中。
如程中:
include
include "a.h"
对于<>,预处理程序cpp在系统预设的头文件目录(/usr/include)中搜索相应的头文件,er
而对于”“,cpp在当前目录中搜索头文件,这个选项是告诉cpp,如果当前目录中没找到需要的头文件,就到指定的dirname目录中寻找。
例:gcc main.c -l /home/include -o main
是先到预设路径中寻找,然后再到/home/include目录下搜索头文件。
-Ldirname:将dirdirname所指出的目录加入到库文件的目录列表中。
在默认状态下,连接程序ld在系统的预设路径中(/usr/lib)寻找所需要的库文件,这个选项告诉连接程序,首先到-L指定的目录中去寻找,然后再到系统预设的路径中寻找。
-lname(小写的L):再连接时,装载名字为”libname.a”的函数库,该函数库位于系统预设的目录或者由-L选项确定的目录下。
例:gcc main.c -L /home/lib -lmain -o main
-static:静态链接库文件
例:gcc -static hello.c -o hello
-Wall:生成所有警告信息
-w:不生成任何警告信息 warning
-DMACRO:定义MACRO宏,等效于再程序中使用#define MACRO
D选项
D选项是用来在使用gcc/g++编译的时候定义宏的。
gcc -DDEBUG -D 后面直接跟宏命,相当于定义这个宏,默认这个宏的内容是1
gcc -DNAME=Peter -D 后面跟 key=value 表示定义key这个宏,它的内容是value
常用场景
-DDEBUG 定义DEBUG宏,可能文件中有DEBUG宏部分的相关信息,用个DDEBUG来选择开启或关闭DEBUG
-Dprivate=public -Dprotected=public 通常用于测试环境,把private 与 protected全替换为public 的。这个还是很常用的,比如我们使用gtest去单元测试,直接测试目标类的成员函数,但是可能目标类的成员函数为了封装性,它可能是protected,所以我们使用这个宏把它全变成public。
GCC 的-U选项可以在命令行取消宏的定义,相当于#undef。
$ gcc -ULIMIT foo.c
上面示例中的-U参数,取消了宏LIMIT,相当于源文件里面的#undef LIMIT。
GCC 的-std=参数(standard 的缩写)还可以指定按照哪个 C 语言的标准进行编译。
$ gcc -std=c99 hello.c
上面命令指定按照 C99 标准进行编译。
注意,-std后面需要用=连接参数,而不是像上面的-o一样用空格,并且=前后也不能有多余的空格。
64位版与32版:
为了适应现在越来越流行的64位系统,经常需要将代码分别编译为32位版和64位版。其次,除了需要生成debug版用于开发测试外,还需要生成release版用于发布。本文介绍了如何利用makefile条件编译来生成这些版本,而且不仅兼容Linux下的GCC,还支持MinGW、TDM-GCC等Windows下的GCC编译器。
一、C程序代码
为了测试条件编译的效果,以下面这个C语言程序为例(gcc64_make.c)——
#include <stdio.h>
#include <assert.h>
// 获取程序位数(被编译为多少位的代码)
int GetProgramBits()
{
return sizeof(int*) * 8;
}
int main(int argc, char* argv[])
{
printf("bits:\t%d\n", GetProgramBits());
assert( argc>1 );
return 0;
}
main函数中,前两条语句的含义为——
第一条语句用于显示当前程序的位数。如果编译为32位版,将会显示“bits: 32”;如果编译为64位版,将会显示“bits: 64”。
第二条语句是一条断言,需要argc变量大于1。如果编译为debug版,若运行时未加命令参数,该断言失败,于是输出错误信息并终止程序;如果编译为release版,所有断言被屏蔽,不会有错误信息。
二、GCC命令行参数
复习一下GCC命令行参数,看看各个版本的区别——
32位版:加上 -m32 参数,生成32位的代码。
64位版:加上 -m64 参数,生成64位的代码。
debug版:加上 -g 参数,生成调试信息。
release版:加上 -static 参数,进行静态链接,使程序不再依赖动态库。加上 -O3 参数,进行最快速度优化。加上-DNDEBUG参数,定义NDEBUG宏,屏蔽断言。
当没有-m32或-m64参数时,一般情况下会生成跟操作系统位数一致的代码,但某些编译器存在例外,例如——
32位Linux下的GCC,默认是编译为32位代码。
64位Linux下的GCC,默认是编译为64位代码。
Window系统下的MinGW,总是编译为32位代码。因为MinGW只支持32位代码。
Window系统下的MinGW-w64(例如安装了TDM-GCC,选择MinGW-w64),默认是编译为64位代码,包括在32位的Windows系统下。
