【编译、链接与构建详解】Makefile 与 CMakeLists 的作用

【编译、链接与构建详解】Makefile 与 CMakeLists 的作用

前言

在大型项目中,通常会使用 C 或 C++ 语言进行开发,而编译、链接、构建等概念,以及相关工具如 GCC、G++、NVCC、CMake、Make、Makefile 等,是每个开发者都无法绕过的重要内容。这些概念虽然至关重要,但往往容易混淆。因此,本文将简明扼要地介绍这些概念,帮助读者更深入地理解编译、链接与构建过程。

源代码(.c、.cpp)

我们编写的代码通常以 .c.cpp 文件的形式存在,这些源代码文件是人类可读的,但计算机无法直接理解和执行。

计算机只能理解二进制的机器语言,因此,需要通过编译将源代码转换为机器可以识别的格式,这就涉及到编译的相关知识。

编译

编译的本质

程序员编写的 .c.cpp 源代码是人类可读的,但计算机无法直接理解。为了让计算机执行这些代码,需要将其转换为计算机能够识别的二进制语言,这个过程就是编译的本质。

编辑的结果

编译的结果是目标文件.o 文件),它包含了经过翻译但尚未完整链接的二进制代码(机器可以理解的语言)。理解目标文件的作用,有助于更深入地掌握编译过程。

编译器(GCC、G++、NVCC 等)

编译器是将源代码翻译为计算机能够理解的目标文件的"翻译官 "。它负责将人类编写的 .c.cpp.cu 等源代码转化为机器可执行的二进制代码。常见的编译器包括:

  • GCC (GNU Compiler Collection):主要用于编译 C 语言的源代码(.c 文件)。
  • G++ :是 GCC 的 C++ 编译器,用于编译 C++ 语言的源代码(.cpp 文件)。
  • NVCC :NVIDIA CUDA 编译器,用于编译并行计算的 CUDA 程序(.cu 文件)。

这些编译器各自对应不同类型的源代码文件,执行代码翻译的任务。

目标文件(.o

什么是 .o 目标文件

.c.cpp 源代码经过编译后,会生成 .o 目标文件。目标文件是计算机可以理解的二进制代码,意味着源代码已经被翻译成机器语言,程序的执行又向前迈进了一步。

为什么单个 .o 目标文件不能直接执行?

目标文件(.o)本身并不能直接运行 ,因为它只是编译后的中间产物 ,尚未构成完整的可执行程序。通常,一个程序由多个 .c.cpp 文件组成,而 main 函数往往位于其中的一个文件中,负责调用其他模块的函数。

可以将程序比作一辆汽车:main 函数相当于车架,而各个 .o 文件代表轮子、方向盘、控制台等组件。单独的 .o 文件只是一个零件,只有经过链接,将所有模块正确拼接在一起,才能形成最终可运行的程序。

要将这些独立的目标文件整合成一个可执行程序,就涉及到链接的过程。

链接

链接的本质

当所有 .c.cpp 代码经过编译后,都会生成 .o 目标文件。这些 .o 文件虽然已经被翻译成机器可以识别的语言,但它们彼此独立,尚无法直接运行。

链接就是组装:

可以将 .o 文件比作汽车的零部件:单独的目标文件就像轮子、方向盘、发动机等组件,只有经过链接 ,将这些部件正确组装起来,才能形成一个完整的可执行程序(拼成一个可以跑的汽车)。链接的本质 ,就是将多个 .o 目标文件整合在一起,最终拼接成可以运行的可执行文件。

通常,在所有被链接的 .o 目标文件中,只有一个包含 main 主函数,它相当于汽车的车架 ,而其他 .o 文件则封装了各种功能模块(如发动机、刹车系统、座椅等)。链接的过程,就是将这个带有 main 入口的 *"车架"与其他 "零部件"*拼接在一起,使其成为一个完整可运行的程序。

如果需要链接的 .o 文件很多且杂乱怎么办?

在大型项目中,编译过程中会生成大量 .o 目标文件。如果直接链接所有 .o 文件,不仅会导致项目结构混乱,还会增加管理和分发的难度。

为了解决这个问题,通常会将多个 .o 文件打包成库文件 ,即 .so(动态/共享库).a(静态库)。这些库文件可以帮助我们更高效地组织、管理和复用代码,使项目结构更加清晰,链接过程也更加简洁。

库文件(.a、.so)

为了更方便地管理大量的 .o 目标文件,引入了库文件 的概念。库文件可以看作是多个 .o 文件的集合,用于提高代码的组织性和复用性。然而,库文件分为两种类型:

  • .a(静态库)
  • .so(动态库/共享库)

二者虽然都是 .o 文件的集合,但在使用方式上存在明显区别。

静态库(.a

静态库(.a)本质上是多个 .o 目标文件的打包集合,在编译时会被直接链接到可执行文件中。这种方式可以提高代码复用性,并减少每次编译时重复编写相同代码的工作量。

然而,静态库是固定的 ,如果库中的 .o 文件对应的源代码发生修改,就需要重新编译修改的部分并更新静态库文件,然后再重新链接生成新的可执行文件。这意味着每次库文件更新后,所有依赖该库的程序都必须重新编译和链接。

动态库(.so

动态库(.so,共享库)与静态库类似,也是多个 .o 目标文件的集合,但它不会在编译时直接嵌入可执行文件 ,而是在程序运行时被加载。这种方式减少了可执行文件的体积,并允许多个程序共享同一个库,从而提高资源利用率。

此外,动态库是灵活的 ,如果库中的 .o 文件对应的源代码发生修改,只需重新编译动态库文件,无需重新编译和链接所有依赖它的程序。程序在运行时会自动加载最新版本的动态库,因此更新更加便捷。

构建

构建是将源代码转化为可执行文件的完整过程,通常包括以下几个主要步骤:

构建的步骤

  1. 清理

    在进行新一轮构建之前,需要先清理掉之前构建的产物,比如删除旧的目标文件 .o、可执行文件和库文件等。这一步确保构建环境干净,避免旧文件影响新一轮构建。

  2. 管理依赖

    项目可能依赖外部库或资源,这时候需要下载、安装并管理这些依赖,确保它们的版本正确且可用。

  3. 编译

    这一阶段将源代码文件(.c.cpp)编译成目标文件(.o)。编译过程将人类可读的代码转化为机器可以理解的中间产物。

  4. 链接

    链接过程将多个目标文件(.o 文件)和库文件(.a.so 文件)整合、拼接成一个完整的可执行文件,最终生成可以运行的程序。

  5. 其他

    在某些构建过程中,还可能涉及其他步骤,如单元测试、部署、打包等,这些步骤根据项目需求可能会有所不同。

自动化构建

如上所述,构建过程包含多个环节,涉及到源代码的编译、目标文件的生成、依赖的管理等操作。为了实现高效且自动化的构建,通常会使用构建工具来控制这一过程。

最常见的构建工具是 Make ,它通过 Makefile 文件定义的构建规则和依赖关系,从而自动化管理和执行构建步骤。接下来,我们将介绍 Make 工具及其 Makefile 构建规则的相关内容。

构建工具与构建规则(Make、Makefile)

Make 是一种常用的构建工具,它根据 Makefile 中定义的 构建规则 来自动化构建过程。Makefile 中指定了构建目标、依赖关系、编译器选项、可执行文件名等具体的构建命令。

然而,Make 的一些局限性也较为明显:它的语法较为底层,可读性较差,并且对多平台兼容性较弱(例如,它通常只适用于 UNIX 系统,而在其他平台的构建可能会遇到困难)。

为了克服这些问题,CMake 应运而生。CMake 是一个更高级的构建配置工具,它通过平台无关的配置文件生成适用于不同平台的构建文件(例如 Makefile 或 Visual Studio 工程文件),从而解决了多平台兼容性和可读性差的问题。

构建配置工具与构建配置文件(CMake、CMakeLists)

正如前文所述,CMake 是一个高级的构建配置工具,它通过读取 CMakeLists.txt 文件中定义的构建配置来生成适应不同平台的构建规则。CMake 会根据 CMakeLists.txt 中的内容,自动生成对应的构建文件(例如 Makefile 或 Visual Studio 工程文件),然后调用相应的构建工具(如 make)来执行构建过程。

相较于 Make ,CMake 提供了更高层次的抽象,具有更强的实用性和更好的跨平台兼容性。因此,CMake 在大型项目中得到了更广泛的应用,尤其是在需要支持多平台构建时,CMake 和 CMakeLists.txt 文件成为了主流的选择。

相关推荐
在野靡生.19 分钟前
Ansible(4)—— Playbook
linux·运维·ansible
Linux技术芯24 分钟前
Linux内核内存管理 ARM32内核内存布局的详细解析和案例分析
linux
烨鹰25 分钟前
戴尔电脑安装Ubuntu双系统
linux·运维·ubuntu
凉白开33833 分钟前
Scala基础知识
开发语言·后端·scala
不要不开心了35 分钟前
Scala内容
开发语言·pytorch·flask·scala·dash
2401_8242568636 分钟前
Scala的函数式编程
开发语言·后端·scala
mzak36 分钟前
vscode集成deepseek实现辅助编程(银河麒麟系统)【详细自用版】
linux·vscode·编辑器·银河麒麟·deepseek
haoranyyy41 分钟前
mac环境中Nginx安装使用 反向代理
linux·服务器·nginx
HX科技1 小时前
Debian系统_主板四个网口1个配置为WAN,3个配置为LAN
linux·运维·网络·debian
幻想趾于现实1 小时前
C# Winform 入门(2)之发送邮件
开发语言·c#