认识编译器和C/C++编译

一. 编译器

编译器也是一种程序,其作用是将一种语言翻译为另一种语言,通常是将高级语言翻译为低级语言,或者说是将源代码翻译成能被计算机或虚拟机执行的目标代码。

编译器的主要工作流程是:源代码-预处理器-编译器-目标代码-链接器-可执行文件

另一个角度的工作流程:词法分析-语法分析-语义分析-中间代码生成-代码优化-目标代码生成-目标代码优化

编译器的种类

“本地”编译器

用来生成与编译器本身所在环境操作系统(平台)相同的环境运行的目标代码的编译器叫“本地”编译器。

“交叉”编译器

生成用来在其他平台上运行的目标代码,这种编译器叫做“交叉”编译器。这个过程也叫交叉编译。

例如:在 Mac 上编译能在 Android 上运行的 so , 这个过程就属于交叉编译。

编译器的前后端

以中间代码生成步骤为中心划分:

  • 与源语言有关,与目标语言无关的部分叫做编译器前端
  • 和目标语言有关,和源语言无关的部分叫做编译器后端

将编译器分为前端和后端,对编译技术的起到了一定作用。

二. GNU & GCC & Clang & llvm

1. GNU

GNU, Gnu's Not Unix 的缩写。由于一开始 Unix 系统是商业收费的,理查德·斯托曼提出 GNU 计划,希望发展出一套完整的开放源代码操作系统来取代 Unix,名为 GNU 。

1989 年,GNU 项目中编辑器、编译器、shell 等都已完成,唯独缺了操作系统核心,所以开始正式发展 Hurd 来作为 GNU 计划的操作系统。

1991 年,Linux 出现, GNU 项目的软件可在 Linux 上运行。

1992 年,Linux 和 GNU 结合,形成完全自由的操作系统,称为 GNU/Linux 简称 Linux 。此时 Hurd 还没完善,被抛弃。

2. GCC

gcc, GNU C Compiler 的缩写,是 GNU 项目的编译器部分,也是类 Unix 和 Mac OS X 操作系统的标准编译器。

gcc 原本只处理 C 语言,后来也发展成可处理 Object-c、Java 、C++。

g++

gcc 和 g++ 都是 GNU 的编译器。他们的区别如下:

  • 对于 .c ,gcc 把它当作 C 程序,而 g++ 当作 C++ 程序;对于 .cpp , gcc 和 g++ 都会当作 c++ 程序。

对于 .cpp 的编译链接
gcc 和 g++ 都可以编译,而链接可以用 g++ 或者gcc -lstdc++。因为 gcc 命令不能自动和 C++ 程序使用的库联接,所以通常使用 -lstdc++ 来完成联接。

3. Clang

Clang 是一个 C、C++、Objective-C 和 Objective-C++ 编程语言的编译器前端。它采用了底层虚拟机(LLVM)作为其后端。这个软件项目是由苹果发起的,目标是替代 GNU 的 gcc 编译器套装。

因为 gcc 的编译器慢慢无法满足苹果的需求,因此,苹果开发了 Clang 与LLVM来完全取代 gcc,Xcode4 之后,苹果的默认编译器已经是 Clang/LLVM 。Clang 作为编译器前端,LLVM 作为编译器后端。

4. MinGw

MinGw 是 Minimalist GNU for Windows 的缩写。它是一个可自由使用和自由发布的 Windows 特定头文件和使用 GNU 工具集导入库的集合,允许在 Windows 平台生成本地的 Windows 程序而不需要第三方 C 运行时(C Runtime)库。

三. C/C++ 编译过程

C/C++ 编译过程可以分为四个步骤:

  • 预处理
  • 编译
  • 汇编
  • 链接

1.预处理

预处理是处理文件中的预处理命令,通常是 # 开头,预处理通常包含如下步骤:

  • 替换宏定义 #define
  • 处理条件预编译指令如 #if
  • 处理 #include 指令,将需要包含的文件递归包含进来
  • 等等

可以使用 -E 命令执行预处理操作,-o 表示生成的文件,最终生成 .i 文件

gcc -E -o test.i test.c


例如:有 test.h test.c 文件

>>test.h
int func(int a,int b) {
    return a + b;
}

>>test.c 

#include "test.h"
#define A 1
#define B 2
int main() {
    
    int c = func(A,B);
}

执行 gcc -E -o test.i test.c

>> 生成的 test.i

# 1 "test.c"
# 1 "" 1
# 1 "" 3
# 363 "" 3
# 1 "" 1
# 1 "" 2
# 1 "test.c" 2

# 1 "./test.h" 1


int func(int a,int b) {
    return a + b;
}
# 3 "test.c" 2


int main() {

    int c = func(1,2);
}

2. 编译

编译的过程是将经过处理之后的程序转换成特定汇编语言代码的过程,可以使用 -E 命令执行编译操作,其表示让编译器编译之后停止,不进行后续步骤。

gcc -S -o test.s test.c

>> 汇编代码:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15, 4
    .globl  _func                   ## -- Begin function func
    .p2align    4, 0x90
_func:                                  ## @func
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %eax
    addl    -8(%rbp), %eax
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function
    .globl  _main                   ## -- Begin function main
    .p2align    4, 0x90
_main:                                  ## @main
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    $1, %edi
    movl    $2, %esi
    callq   _func
    xorl    %ecx, %ecx
    movl    %eax, -4(%rbp)
    movl    %ecx, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

3.汇编

汇编是将上述汇编代码转换为机器码,这一步产生的文件叫做目标文件,是二进制格式。每一个源文件都会产生一个 .o 的目标文件。

as test.s -o test.o

等价于

gcc –c test.c –o test.o

>> 这里就不贴

4.链接

链接过程是将目标文件以及所需的库文件,链接成最终的 .out 可执行文件。链接程序的主要工作就是将有关的 .o 的目标文件彼此相连接,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

>> 执行
gcc test.c

生成 a.out 的可执行文件

在 mac 上执行 ./a.out 可运行文件

四. 静态链接和动态链接

静态链接

静态链接:在链接阶段,将汇编生成的目标文件 .o 与引用到的库一起链接打包进可执行文件中。就是在编译链接时直接将需要的执行代码拷贝到调用处,因此允许的时候存在多份内存拷贝。

静态链接库:一组目标文件的的集合,即很多目标文件经过压缩后打包形成一个文件 .a

静态链接库的特点:

  • 静态库对函数库的链接是放在编译时期完成的
  • 程序在运行的时候与函数库无关
  • 浪费内存,多个地方调用某个函数就存在多次拷贝

动态链接

就是在编译的时候不直接拷贝可执行的代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中。

然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。

动态链接库的特点:

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
  • 多个程序可以共享同一段代码,而不需要在内存上存储多个拷贝,
  • 缺点是由于是运行时加载,可能会影响程序的前期执行性能。
不同操作系统下编译过程文件的后缀
系统 源文件 目标文件 动态链接库 静态链接库 可执行文件
windows .c/.cpp .obj .dll .lib .exe
Linux .c/.cpp .o .so .a .out/coff/elf (没有后缀)
Mac OS X .c/.cpp .o .dylib .a .out/coff/elf (没有后缀)
参考文章

编译器
编译器(GNU & GCC & clang & llvm)
C语言编译过程详解
Linux 中的动态链接库和静态链接库是干什么的

你可能感兴趣的:(认识编译器和C/C++编译)