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系统下。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注