# 编译

# 编译器

  • C 语言是编译型语言。源代码不能直接作为程序运行,需要用 gcc 等工具,编译成可执行文件(内含二进制机器代码),才能运行。
  • 负责编译工作的工具有多种,统称为编译器。

常见的几种 C 语言编译器:

  • gcc(GNU C Compiler)
    • 最初只支持编译 C 语言的源代码,后来还支持 C++、Objective-C、Fortran、Java 等语言,并且被移植到多个平台。
    • 是 GNU 编译器集合(GNU Compiler Collection)的一部分,后者是类 Unix 系统上的标准编译器。
  • g++
    • gcc 提供的另一种编译命令,本质上也是 gcc 编译器。
    • 支持编译 C、C++ 语言,但默认将源代码当作 C++ 语言处理。
  • MSVC(Microsoft Visual C++)
    • 支持编译 C、C++ 语言。
  • Clang
    • 支持编译 C、C++、Objective-C 等语言。
    • 基于 LLVM 编译器运行。

# gcc

gcc 基本的编译流程如下:

  1. 预处理(Pre-Processing):主要是处理 C 语言源文件中的预处理命令,生成一个中间文件 *.i

    gcc -E test.c -o test.i
    
    • -o 选项是控制生成的文件名。
  2. 汇编(Assembling):由中间文件 *.i 生成汇编语言文件 *.s

    gcc -S test.i -o test.s
    
  3. 编译(Compiling):由汇编语言文件 *.s 生成目标文件。

    gcc -c test.s -o test.o
    
    • 目标文件又称为对象文件,它是二进制文件,与最终的可执行文件很像。
  4. 链接(Linking):由目标文件生成最终的可执行文件。

    gcc test.o -o 1
    
  • 上述四个步骤可以简化为一个步骤,称为 构建(Build)

    gcc test.c -o test
        -I /root/test/include  # 添加头文件的检索目录
        -L /root/test/lib      # 添加库文件的检索目录
        -l lib1                # 链接一个名为 lib1 的库文件
        -static                # 只使用静态链接库
    
    • 使用了头文件或库文件时,gcc 会先检索-I、-L 指定的目录,再检索环境变量 PATH 路径。
  • 编译时,可能遇到几种报错:

    • error :致命错误,会导致编译失败。
    • warning :警告,此时程序可以编译、链接,但运行时可能出错。
      • 比如用户定义了一个变量,却没有使用它,则编译时会警告:unused variable
      • 比如 ANSI C 标准没有规定,访问数组越界的结果是什么。这种操作称为 undefined behavior ,具体结果由各个编译器决定,可能报错,也可能不报错。

# 构建

  • 当项目中源文件较多、引用关系较复杂时,用编译器逐个编译比较麻烦,建议使用构建工具。
    • 构建工具能调用编译器,自动化地编译大量源文件,并进行链接、打包等操作。
  • 构建工具举例:
    • make :通常调用 gcc 作为编译器,根据 Makefile 文件,构建项目。
    • cmake :可以根据 CMakeList.txt 文件自动生成 Makefile 文件。在 Windows 上则是生成 visual studio 的 project 文件。
    • qmake :用于生成 Qt 项目的 Makefile 文件。
    • 有的 IDE 本身提供了构建项目的功能,或者调用某种构建工具。

# make

:一个命令行工具,用于构建 C 语言项目。

  • 命令:
    make           # 开始编译(默认使用当前目录下的 Makefile)
        clean      # 删除所有被 make 创建的文件
        install    # 编译并安装到系统中
        uninstall  # 卸载
    
  • make 在执行时,会读取 Makefile 文件中的指令,获取模块间的依赖关系,判断哪些文件过时了需要重新编译,然后生成中间代码或最终的可执行程序。

# 跨平台

  • 假设你在主机 A 上编写了一个 C 语言程序,如何分享给主机 B 使用?

    • 可以将主机 A 上编译的二进制文件,拷贝到主机 B 。
      • 优点:
        • 主机 B 拿到文件,可以直接运行。
      • 缺点:
        • 主机 B ,可能与主机 A 差异大(比如操作系统不同、CPU 架构不同),不能运行主机 A 编译的二进制文件,这称为不兼容。
    • 可以将主机 A 上的源文件,拷贝到主机 B 。
      • 优点:
        • 即使主机 B ,与主机 A 差异大,也可以重新编译生成二进制文件,运行效果与主机 A 相同。(除非两个主机的编译器,差异大)
      • 缺点:
        • 主机 B 拿到源文件,需要编译,才能运行。这增加了耗时,还需要安装编译工具。
  • 假设将主机 A 上编译的二进制文件,拷贝到主机 B ,如何保证兼容性(又称为跨平台)?

    • 有的编译工具,支持交叉编译。在主机 A 上,刻意编译出兼容主机 B 的二进制文件。
    • 比如主机 A 是 64bit CPU ,可以编译出兼容 32bit CPU 的二进制文件,能在主机 B 上运行,不能在主机 A 上运行。
  • C 语言的跨平台挺麻烦,但其它高级编程语言擅长跨平台。比如 Java 通过 JVM 的机制与底层平台解耦,只需编译一次,就可以拷贝到多个平台上直接运行。

# 文件格式

  • 在类 Unix 系统中:

    • 可执行文件、库文件的内容格式,都遵循 ELF(Executable and Linkable Format,可执行和可链接格式)。
    • 可执行文件通常没有扩展名,或者扩展名为 .bin
    • 目标文件的扩展名为 .o
    • 动态链接库的扩展名为 .a
    • 静态链接库的扩展名为 .so
  • 在 Windows 系统中:

    • 可执行文件的扩展名为 .exe
    • 目标文件的扩展名为 .obj
    • 动态链接库的扩展名为 .lib
    • 静态链接库的扩展名为 .dll

# 库文件

:包含了一些二进制代码,可以被程序导入、调用。

  • 文件名的格式为 lib + 库名 + 扩展名

  • 用途:

    • 可以将程序的代码分离成多个模块,方便调整。
    • 可以将一个程序的部分代码解耦出来,保存在库文件中,给另一个程序调用,提高代码的复用性。
    • 可以将某种语言的程序保存在库文件中,给另一种语言的程序调用,提高代码的通用性。
  • 分类:

    • 动态链接库
      • :又称为共享库,在程序运行时才被动态导入。
      • 这会减小程序文件的体积,但在运行时需要花时间加载。
    • 静态链接库
      • :在程序编译时的 "链接" 阶段被导入,保留在可执行文件中。
      • 这会增加程序文件的体积,但运行时不必花时间加载。
  • 例:从目标文件生成动态链接库

    gcc -shared test.o... -o test.so    # 可以调用多个目标文件,还可以从.c 文件生成
        -fPIC      # 使生成的代码全部采用相对地址,这样就可以被加载到内存的任意位置
        -shared    # 生成共享动态链接库
    
    • gcc 在编译时会根据库名去寻找相应的库文件,且优先使用动态链接库,当动态链接库文件不存在时才寻找相同库名的静态链接库。
  • 例:从目标文件生成静态链接库

    ar -rc lib1.a 1.o  ...          # 可以加上多个目标文件
    

# 相关概念

  • MinGW(Minimalist GNU For Windows)
    • 该项目将 GNU 编译器集合移植到了 Windows 上,让用户可以在 Windows 上使用 GNU 编译器(一般是在 DOS 窗口中使用),生成扩展名为 .exe 的可执行文件。
    • 在编译时,会将源代码中调用的 Linux 库文件、API 转换成 Windows 库文件、API 。
  • Msys
    • :MinGW 的子项目,提供了一个在 Windows 上运行的简单 Linux 系统。
  • Cygwin
    • :一个可以在 Windows 上运行的简单 Linux 系统。
    • Cygwin 将一些 Linux 特有的库文件、API 移植到了 Windows 上,模拟出 Linux 的运行环境。被 Cygwin 编译出的程序在运行时会调用这些库文件。
    • 在 2020 年代以前,很多人会使用 Cygwin ,从而在 Windows 系统上运行 Linux 软件。但目前 Cygwin 很少被使用,因为 Windows 提供了 Windows 的 Linux 子系统(Windows Subsystem for Linux ,WSL),功能更强。
  • Visual Studio
    • :微软公司提供的一个重量级、功能丰富的 IDE ,支持 C、C++、C# 等语言的开发、调试、构建。
    • 可用于在 Windows 上编译 C、C++ 代码,而且比 MinGW 的功能更强。
    • 提供了 MSVC 编译器,以及 msbuild、cl、link、nmake 等命令行工具。
  • Qt
    • :一个跨平台的 C++ 开发框架。
    • 提供了一些与平台解耦的 C++ 库文件,因此在该框架中开发的程序可以直接编译成 Linux、Windows、MacOS 等多种平台的可执行文件。