# 编译

# 编译器

编译器:泛指用于编译源代码的工具。

常见的几种 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 选项表示 output
    
  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 :警告,可以成功编译、链接,但运行时可能出错。

# 文件格式

  • 在类 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  ...          # 可以加上多个目标文件
    

# 构建

  • 构建工具能调用编译器,自动化地编译大量源文件,并进行链接、打包等操作。
    • 当项目中源文件较多、引用关系较复杂时,用编译器逐个编译比较麻烦,建议使用构建工具。
  • 常见的构建工具:
    • 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 文件中的指令,获取模块间的依赖关系,判断哪些文件过时了需要重新编译,然后生成中间代码或最终的可执行程序。

# 跨平台移植

  • 将一个程序移植到不同平台上运行,主要有两种方式:
    • 主机编译:如果要在某个平台上运行一个程序,则先将程序源代码拷贝到该平台,编译成可执行代码。
    • 交叉编译:在一个平台上生成另一个平台上的可执行代码。常用于另一个平台缺少编译环境的情况,比如小型嵌入式系统。
  • 在 Linux 上编译程序时:
    • 如果程序没有调用 Linux 上特有的库文件、API ,则可以直接放到 Windows 上运行。
    • 如果程序依赖 Linux ,则可以用 Visual Studio、MinGW、Cygwin 等工具编译成 Window 的可执行文件。

# 相关概念

  • 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 编译出的程序在运行时会调用这些库文件。
  • Visual Studio
    • :微软公司提供的一个重量级、功能丰富的 IDE ,支持 C、C++、C# 等语言的开发、调试、构建。
    • 可用于在 Windows 上编译 C、C++ 代码,而且比 MinGW 的功能更强。
    • 提供了 MSVC 编译器,以及 msbuild、cl、link、nmake 等命令行工具。
  • Qt
    • :一个跨平台的 C++ 开发框架。
    • 提供了一些与平台解耦的 C++ 库文件,因此在该框架中开发的程序可以直接编译成 Linux、Windows、MacOS 等多种平台的可执行文件。
  • 一些编程语言本身就支持跨平台。比如 Java 通过 JVM 的机制与底层平台解耦,只需编译一次,就可以拷贝到多个平台上直接运行。