GNU Compiler Collection

GCC 是 GNU Compiler Collection 的简称,GCC 原本称为 GNU C Compiler,随着时代演进,陆续支援 Fortran 、 Pascal 、 Objective-C 、 Java 、 Ada 、 Go 等程序语言,才改称 GNU Compiler Collection。
本篇文章只会探讨 C 语言的部分,不会对其他程序语言多做介绍。

关於 GNU 计画,可参考未来几天会发出的 UNIX、BSD 与 Linux 的爱恨情仇一文。

C 语言的编译流程


要将 C 语言编译成可执行档,需要经过多个流程:

  1. 预处理
    将 include 、 macro 扩展成真正的内容,经过这一步骤後,档案会变大许多。
    使用 GCC 进行预处理的方式如下:

    gcc -E -I./src main.c -o main.i
    
    • -E 会让 gcc 仅对 c 程序做预处理
    • -I 用来指定自定义标头档的优先搜寻目录,若你定义的标头档与程序码同目录,就可以忽略该参数。
    • -o 指定输出档案的档名与格式
  2. 编译
    将预处理过的程序码编译成组合语言,而组合语言又会因为不同的处理器架构出现差异,如果要在 x86 平台编译出 ARM 平台能执行的程序码,就会需要做交叉编译。
    使用 GCC 进行编译的方法:

    gcc -S -I./src main.c -o main.s
    
    • -S 仅输出 C 程序对应的组合语言
  3. 组译
    经过编译後,我们会得到组合语言档案,也就是 *.s 档案。不过,若要让电脑能够执行我们的程序码,我们仍需要将其转换成机器码。

    gcc -c main.s -o main.o
    
    • -c 仅编译 C 程序,但不连结,即输出对应的目标码
  4. 连结
    连结会将多个目标文件或是 library 连结成可执行档。

    gcc -o main main.o
    

    得到可执行档後,可以在 shell command 输入以下命令执行程序:

    ./main
    

    如果有多个机器码最後会被连结成一个可执行档案,也可以这麽做:

    gcc -o main a.o b.o c.o
    

    这样一来,如果开发者更动了其中一个档案,我们只需对有更动的档案进行编译再连结起来。可以大幅节省编译的时间。

其他常见参数

除了最基本的编译,当然还要学习其他 GCC 内建的强大功能!

程序最佳化

GCC 可以针对不同的处理器架构对程序进行最佳化:

  • [ ] O0
  • [ ] O1
  • [x] O2 (常用)
  • [ ] Os
  • [ ] O3 (最佳化)
gcc -O2 -o main main.c

显示错误资讯

考虑以下程序码:

#include <stdio.h>
int main(){
    int n;
    printf("number is %d\n", n);
    return 0;
}

该程序中,n 并没有被正确初始化,不过 GCC 预设是不会有错误提示的,若开发者有需要,可以使用 -Wall 参数:

gcc -Wall -o main main.c

使用 GNU Debugger

若我们需要使用 GDB 进行除错,在编译之前,我们需要添加 -g 参数告知 GCC 要尽可能的提供资讯给 GDB :

gcc -Wall -g -o main main.c

verbose mode

我们可以使用 -v 参数启用 verbose mode,获得详细的编译讯息:

gcc -v main.c

Macro

假设开发者在程序中埋了一段程序码,该程序码只被用於除错:

#include <stdio.h>
int main(){
    int n = 0;
    for(int i =0;i<1000;i++){
        n += i;
        #ifdef DEBUG
        printf("n is %d\n", n)
        #endif
    }
    return 0;
}

我们除了可以在程序码中加入 #define DEBUG 外,还可以直接对 GCC 下 -D 参数:

gcc -DDEBUG -o main main.c

除了可以用 -D 定义 Macro,GCC 还允许开发者使用 -U 参数取消定义:

gcc -UDEBUG -o main main.c

标头档目录

刚刚已经在预处理的部分提到 -I 的用途。此外,GCC 在编译程序时只预设引入一部份的标头档,如果程序需要使用特别的 Library 进行编译,可以使用 -l-L 参数:

  • -l 告知 GCC 编译程序时需要使用这个 Library :
    以 POSIX Thread 为例:

    gcc -lpthread -o main.out main.c
    

    需要注意的是,这里的 Library name 不等於档案名称,在 Linux 下的函式库都要使用 lib 开头,其中 *.so 是动态连结函式库,*.a 是静态连结函式库。
    由此可知,把 File name 的 lib.so 去掉就是 Library name 了。

  • -L
    只要函式库的存放路径为:

    • /lib
    • /usr/lib
    • /usr/local/lib

    都可以直接使用 -l 进行连结,换句话说,如果 Library 的位置并非上述的目录,我们就需要用 -L 告知 GCC Library File 所在的目录位置:

    gcc -lsum -L/home/gtwang/lib -o main.out main.c
    

查找依赖关系

Makefile 可以让开发者省略复杂的编译选项及参数,考虑以下专案结构:

|-- project
    |-- src/
    |   |-- main.c
    |   |-- linked-list.c
    |   |-- linked-list.h
    |   |-- node.c
    |   |-- node.h

并且,在 main.c 中引用了 linked-list.h,当我们使用下面命令,就可以查找出 main.c 的依赖关系:

gcc -MM main.c

输出:

main.o: main.c linked-list.h

再假设 linked-list.h 中有使用到 node.h,输出就会是:

main.o: main.c linked-list.h node.h

如果希望将标准库的依赖关系也列出来,使用 -M 即可:

gcc -M main.c

关於依赖关系,还有相当多种参数可以玩,详细资讯可以参考 Reference 的最後一项。

Reference


<<:  Day 13 : 程序除错与异常

>>:  Day11 HTML一

科学家与研究生的关系 研究篇

小的时候,曾有个做科学家的梦想,但不得其门而入,直到上了研究所,才慢慢了解科学家的由来。 在台湾,国...

Day 11 - BOM (Browser Object Model)

BOM (Browser Object Model) 浏览器物件模型 JavaScript 与浏览器...

Google无法移除侵权网址127.0.0.1

故事从 127.0.0.1这个网址用於localhost,表示为本机端,也会被用来测试网路状况......

[30天 Vue学好学满 DAY6] 计算属性(Computed)

计算属性(Computed) 无传入值 具回传值(return) 对来源属性进行操作-> 触发...

服务链接(service mesh)不可能在基於微服务的应用程序中直接与客户端交互

-API 闸道器和服务网格(来源:Liran Katz) 实施 API 闸道器以促进跨境通信;他们...