目录

GUN 计划

在上世纪 7-80 年代,应用最为广泛的 UNIX 操作系统通常是一个闭源的商业软件。1983 年,麻省理工学院的程序员理查德·斯托曼提出了 GNU 计划,希望发展出一套完整的、开发源代码的操作系统以取代 UNIX,志在创建一个完全兼容 UNIX 的自由软件生态环境。

我们知道,一个完整的操作系统是需要包含许多软件的,除了最重要的操作系统内核之外,还需要有编辑器,编译器,Shell、视窗系统等等一系列软件作为支撑。直到 1989 年,GNU 计划中的其他部份都已经完成了,独缺一个操作系统内核。

1990 年,自由软件基金会开始将 Hurd 内核加入 GUN 计划。第二年,Linux 诞生,由于 Linux 诞生即开源,其良好的开放性使得几乎所有 GNU 计划中的、运行于用户空间的软件都可以在 Linux 上使用。于是许多开发者开始转向于 Linux,参与了 Linux 的开发与修改,Linux 也逐步成为了最受欢迎的 GNU 软件开发及运行平台。相反的,Hurd 内核直到 2013 年为止都还没能发布稳定的版本。

1992 年,Linux 与 GNU 计划结合,一个完全自由的操作系统正式诞生了。当时,理查德·斯托曼主张,因为 Linux 使用了许多的 GNU 软件,所以应该正名为 GNU/Linux,但这一提议并没有得到 Linux 社区的一致认同,后来还引发了 GNU/Linux 命名争议。但不管如何,虽然 Linux 本身并不属于 GNU 计划的一部份,但两者的关系早已宛如一体共生。

虽说 GNU 计划中的 Hurd 内核发展得并不如意,但 GNU 工程十几年以来创造了无数的著名的自由软件,例如:强健的 GCC 编译器,TeX 文本编辑器,X Window 视窗系统等等。

GCC 编译器

GCC(GNU Compiler Collection,GNU 编译器套件)是 Linux 下使用最广泛的 C/C++ 编译器。GCC 是以 GPL 许可证所发行的自由软件,也是 GNU 计划的关键部分。GCC 的初衷是为 GNU 操作系统专门编写一款编译器,现已被大多数类 Unix 操作系统,如:Linux、BSD、Mac OS X 等采纳为标准的编译器。GCC 支持多种计算机体系结构芯片,如 x86、ARM、MIPS 等,并已被移植到其他多种硬件平台。

GCC 仅仅是一个编译器,没有界面,必须在命令行模式下使用。通过 gcc 命令就可以将源文件编译成可执行文件。

上述示例通过 gcc 命令一次性完成编译和链接的整个过程,这样最方便。但实际上,gcc 命令也可以将编译和链接分开,每次只完成一项任务:

  1. 编译(Compiler):就将 hello.c 编译为 hello.o,一个源文件只会生成一个目标文件,默认的目标文件名字和源文件名字是一样的。
gcc -c hello.c
  1. 链接(Linker):在 gcc 命令后面紧跟目标文件的名字,就可以将目标文件链接成为可执行文件。
gcc hello.o

Clang 和 LLVM

GCC 目前作为跨平台编译器来说它的兼容性无异是最强大的,但兼容性肯定是以牺牲一定的性能为基础的。我们知道,在整个编译过程中,以中间代码为界,前面的词法分析、语法分析、语义分析等称之为前端处理,而后面的代码优化和目标代码生成称为后端处理。

GCC 作为兼容性最好的编译器,其为不同的高级语言单独写了一个前端,同时也为不同的处理器架构单独写了一个后端。你可以下载一份 GCC 源代码,通过配置 configure 来生成自己需要的编译器类型。但实际上这件事情并不容易,因为前端的主要功能是产生一个可供后端处理的抽象语法树,而语法树的结构实际上很难与处理器架构脱钩,这些都是编译器应用中需要解决的问题。

苹果为了能够针对自家的 Objective-C 编程语言提高编译性能,因此为 MAC 系统开发了一套专用的编译器软件 Clang 和 LLVM。自 XCode4 之后,苹果的默认编译器已经是 LLVM 了。

  • Clang 作为编译器前端:目的是输出代码对应的抽象语法树(Abstract Syntax Tree, AST),并将代码编译成 LLVM Bit Code。
  • LLVM 作为编译器后端:将 LLVM Bit Code 编译成平台相关(e.g. x86、ARM)的机器语言。

Clang 生成的 AST 所耗用掉的内存仅仅是 GCC 的 20% 左右。测试证明 Clang 编译 Objective-C 代码时,速度为 GCC 的 3 倍,还能针对用户发生的编译错误准确地给出建议。

GCC 的常用指令选项

  • -c:只编译,不链接成为可执行文件,通常用于编译不包含主程序的子程序文件。
  • -o <output_filename>:确定输出文件的名称,默认为 XXX.out。
  • -g:产生 GDB 符号调试工具所必要的符号信息,要对源代码进行调试,就必须加入这个选项。
  • -O:对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、链接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是编译、链接的速度就相应地要慢一些。
  • -O2:比 -O 更好的优化编译、链接,当然整个编译、链接过程也会更慢。
  • -I <dirname>,将 dirname 指向的目录加入到 C 程序的头文件目录列表中,是在预处理过程中使用的参数。C 程序中的头文件包含两种情况∶
    • #include <myinc.h>:预处理程序 cpp 在系统预设包含文件目录(e.g. /usr/include)中搜寻相应的文件。
    • #include "myinc.h":预处理程序 cpp 在目标文件的文件夹内搜索相应文件。
  • -v:打印 gcc 执行时的详细过程。

GCC 所遵循的部分约定规则

  • .c 文件:C 语言源代码文件;
  • .h 文件:是程序所包含的头文件;
  • .o 文件:是编译后的目标文件;
  • .a 文件:是由目标文件构成的档案库文件;
  • .C、.cc 或 .cxx 文件:是 C++ 源代码文件且必须要经过预处理;
  • .i 文件:是 C 源代码文件且不应该对其执行预处理;
  • .ii 文件:是 C++ 源代码文件且不应该对其执行预处理;
  • .m 文件:是 Objective-C 源代码文件;
  • .mm 文件:是 Objective-C++ 源代码文件;
  • .s 文件:是汇编语言源代码文件;
  • .S 文件:是经过预处理的汇编语言源代码文件。

GCC 的编译流程

虽然我们称 GCC 是 C 语言的编译器,但由 C 语言源代码文件到生成可执行文件的过程不仅仅是编译的过程,而是要经历以下四个相互关联的步骤:

  1. 预处理(Preprocessing):GCC 首先调用预处理程序 cpp 进行预处理,在预处理过程中,.c 文件中的文件包含(include)、预处理语句(e.g. 宏定义 define 等)进行分析,并替换成为真正的内容。

  2. 编译(Compilation):接着调用 cc1 程序进行编译,这个阶段根据输入文件生成 .i 目标文件。

  3. 汇编(Assembly):汇编过程是针对汇编语言的步骤,调用 as 程序进行工作,一般来讲,.S 文件和 .s 文件经过预处理和汇编之后都会生成以 .o 的目标文件。

  4. 链接(Linking):当所有的目标文件都生成之后,GCC 就调用 ld 来完成最后的链接工作。所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中链接到合适的地方。

GCC 的编译流程示例

示例代码

#include<stdio.h>

int main(void)
{
printf("hello\n");
return 0;
}

预处理过程:这个过程处理宏定义和 include,去除注释,不会对语法进行检查。

gcc -E -I . main.c -o main.i

# -E 是让编译器在预处理之后就退出
# -I 指定头文件目录
# -o 指定输出文件名

下面可以看到预处理后,代码从 7 行扩展到了 845 行。.i 文件里面包含了所有 include 和宏定义的真正内容。

# 1 "main.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "main.c"
# 1 "/usr/include/stdio.h" 1 3 4
... # 28 "/usr/include/bits/types.h" 2 3 4
... typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
...

编译过程:这个阶段,检查语法,生成汇编代码。

gcc -S -I . main.i -o main.s

# -S 让编译器在编译之后停止

下面可以看到编译后的汇编代码。

        .file   "main.c"
.section .rodata
.LC0:
.string "hello"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
.section .note.GNU-stack,"",@progbits

汇编过程:这个阶段,生成目标代码,即将汇编代码转换成机器码。这时候的 main.o 文件就不是肉眼可以看明白的了。

$ gcc -c main.s -o main.o
# or
$ as main.s -o main.o $ file main.o
main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接过程:将多个目标文以及所需的库文件(e.g. .so etc.)链接成最终的可执行文件。

$ gcc main.o -o main.exe
# or
ld -o main.exe main.o inc/mymath.o ...libraries...

链接分为两种,一种是静态链接,另外一种是动态链接。静态连接就是把外部函数库,拷贝到可执行文件中,好处是适用范围比较广,依赖的动态链接库较少,对动态链接库的版本不会很敏感,具有较好的兼容性,不用担心用户机器缺少某个库文件。缺点是安装包会比较大,而且多个应用程序之间,无法共享库文件;动态连接的做法正好相反,外部函数库不进入安装包,只在运行时动态引用。好处是安装包会比较小,多个应用程序可以共享库文件;缺点是用户必须事先安装好库文件,而且版本和安装位置都必须符合要求,否则就不能正常运行。

现实中,大部分软件采用动态连接,共享库文件。这种动态共享的库文件,Linux 平台是后缀名为 .so 的文件,Windows 平台是 .dl l文件, Mac 平台是 .dylib 文件。

运行程序

$ ./main.exe
hello

编译多个文件

  • main.c
#include "hello.h"

int main(void)
{
print("hello world");
return 0;
}
  • hello.c
#include "hello.h"

void print(const char *str)
{
printf("%s\n", str);
}
  • hello.h
#ifndef _HELLO_H
#define _HELLO_H #include <stdio.h> void print(const char *str); #endif

一次性编译:

$ gcc -Wall hello.c main.c -o main

$ ./main

独立编译

$ gcc -Wall -c main.c -o main.o
$ gcc -Wall -c hello.c -o hello.o
$ gcc -Wall hello.o main.o -o newmain $ ./newmain

程序编译流程与 GCC 编译器的更多相关文章

  1. 编译安装与gcc编译器

    先说一下gcc编译器,只知道这是一个老款的编译器.编译的目的也比较重要,就是将c语言的代码编译成可以执行的binary文件. gcc 简单使用(后期补充) eg: gcc example.c    # ...

  2. C/C++程序编译流程(预处理->编译->汇编->链接)

    程序的基本流程如图: 1. 预处理 预处理相当于根据预处理指令组装新的C/C++程序.经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内 ...

  3. C/C++程序编译流程

    单个文件的编译过程 多个文件的编译过程

  4. Gcc的编译流程分为了四个步骤:

    http://blog.csdn.net/xiaohouye/article/details/52084770(转) Gcc的编译流程分为了四个步骤: 1.预处理,生成预编译文件(.文件): Gcc ...

  5. [国嵌笔记][012][GCC程序编译]

    GCC特点 GCC(GUN C Compiler)是GUN推出的功能强大.性能优越的多平台编译器.其执行效率与一般编译器相比平均效率要高20%~30%. GCC基本用法 gcc [options] f ...

  6. GCC编译器原理(三)------编译原理三:编译过程---预处理

    Gcc的编译流程分为了四个步骤: 预处理,生成预编译文件(.文件):gcc –E hello.c –o hello.i 编译,生成汇编代码(.s文件):gcc –S hello.i –o hello. ...

  7. GCC编译流程浅析

    GCC-GCC编译流程浅析 序言 对于大多数程序员而言,大家都知道gcc是什么,但是如果不接触到linux平台下的开发,鲜有人真正了解gcc的编译流程,因为windows+IDE的开发模式简直是一条龙 ...

  8. GCC 编译流程简介

    GCC-GCC编译流程 序言 对于大多数程序员而言,大家都知道gcc是什么,但是如果不接触到linux平台下的开发,鲜有人真正了解gcc的编译流程,因为windows+IDE的开发模式简直是一条龙全套 ...

  9. GCC编译流程及常用编辑命令

    GCC 编译器在编译一个C语言程序时需要经过以下 4 步: 将C语言源程序预处理,生成.i文件. 预处理后的.i文件编译成为汇编语言,生成.s文件. 将汇编语言文件经过汇编,生成目标文件.o文件. 将 ...

  10. GCC编译器编译链接

    在gcc编译器环境下,常见的文件扩展名的含义如下: .c:C源程序,经过预编译后的源程序也为.c文件,它可以通过-E参数输出. .h:头文件 .s:经过编译得到的汇编程序代码,它可以通过-S参数输出. ...

随机推荐

  1. #树状数组,概率,离散,双指针#洛谷 6834 [Cnoi2020]梦原

    题目 分析 如果是序列(\(k=1\))也就是积木大赛 那也就是\(\sum_{i=1}^n\max\{a_i-a_{i-1},0\}\) 那关键就是要处理与父节点之间的关系,如果父节点的值小于该节点 ...

  2. #cdq分治,树状数组#洛谷 5459 [BJOI2016]回转寿司

    题目 求 \[\sum_{i=1}^n\sum_{j=i}^{n}[L\leq \sum_{k=i}^j a_k\leq R] \] 分析(树状数组) 考虑前缀和,改为是否有两个数的差在\([L\si ...

  3. HUAWEI AppGallery Connect全新升级,支持HarmonyOS生态全生命周期服务!

     原文:https://mp.weixin.qq.com/s/7aNIplUBdm_D1yyiMrQdAw,点击链接查看更多技术内容.     HUAWEI AppGallery Connect全新升 ...

  4. input 去除默认样式

    前言 如何不自己写框架,基本用不上. 正文 input{ border: 0px; background-color: none; outline: none; } input:focus{ outl ...

  5. android 找不到设备

    前言 当我们安装android studio的时候,测试的时候,你可能找不到设备. 我遇到的有两种情况,一种是本身就需要安装插件,如一些低端机或者有些小米机. 还有一种情况需要去触发一下,有些华为手机 ...

  6. Spark3.0 Standalone模式部署

    之前介绍过Spark 1.6版本的部署,现在最新版本的spark为3.0.1并且已经完全兼容hadoop 3.x,同样仍然支持RDD与DataFrame两套API,这篇文章就主要介绍一下基于Hadoo ...

  7. RocketMQ 之 IoT 消息解析:物联网需要什么样的消息技术?

    前言: 从初代开源消息队列崛起,到 PC 互联网.移动互联网爆发式发展,再到如今 IoT.云计算.云原生引领了新的技术趋势,消息中间件的发展已经走过了 30 多个年头. 目前,消息中间件在国内许多行业 ...

  8. 从 Flink Forward Asia 2021,看Flink未来开启新篇章

    ​简介:本文将对FFA Keynote议题作一些简单的归纳总结,感兴趣的小伙伴们可以在FFA官网[2]找到相关主题视频观看直播回放. ​ 作者 | 梅源(Yuan Mei) 来源 | 阿里技术公众号 ...

  9. 多任务多目标CTR预估技术

    ​简介: 多目标(Multi Objective Learning)是MTL中的一种.在业务场景中,经常面临既要又要的多目标问题.而多个目标常常会有冲突.如何使多个目标同时得到提升,是多任务多目标在真 ...

  10. [GPT] 同为 nodejs 库的 Puppeteer 和 cheerio 的区别是什么

    Puppeteer 和 cheerio 是两个完全不同的库,用途和功能也截然不同. Puppeteer 是一个 Node.js 库,它使用 Chrome 或 Chromium 浏览器作为渲染引擎,通过 ...