最新公告
  • 欢迎您光临欧资源网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • 一聊:如何组织C项目的源代码目录结构与编译流程

    你好,我的名字是TT。

    在前面的内容中,我们遇到了很多示例代码。这些代码的共同点是它们都非常小,可以组织成一个 .c 文件。并且,通过一个简短的一行命令,我们可以同时编译代码和运行程序。

    但在现实中,C 项目往往没有那么简单。有数百个源文件,各种外部依赖项和配置项,使事情变得复杂。因此,当C项目规模由小变大时,如何组织其源代码的目录结构和编译过程就成为我们必须关注的两个问题。今天,我们来谈谈如何看待这两个问题。

    如何组织一个C项目的源代码目录结构?

    让我们从与源目录结构相关的主题开始。实际上,对于一个C项目的源代码目录结构应该采用哪种组织方式,通常没有所谓的“最佳实践”,而是具体问题具体分析。

    对于小型项目,我们可以简单地将.h和.c这两种类型的源文件汇总到两个单独的目录中,include和src,甚至可以将它们混合在同一个目录中。当项目逐渐变大时,可以根据功能将不同的C源文件划分为更详细的划分。

    比如可以以模块的形式抽象成库的形式,可以在名为libs的目录下进行管理。使用库接口实现的应用程序代码可以放在名为 src 的目录中。其他与 C 源代码不直接相关的文件可以自由存放在项目根目录中,也可以放在以相应类别命名的单独目录中。

    在下图中,我给出了两个可以参考的目录结构。然而,重要的是要注意,没有默认或最佳的 C 项目目录结构,无论您采用哪种形式,您都必须学会随着项目的增长而适应。

    对于源代码目录结构的组织,一个基本原则是“清晰易懂”。其中,“清晰”是指即使不知道具体实现,也只能将项目代码的目录树逐层展开,从上到下的方式在代码层面了解其基本组成结构。. “通俗易懂”是指在上述过程中,通过观察文件夹和文件的名称,可以对项目的基本功能和模块化实现有一个大致的印象。

    如何组织C项目的编译流程?

    随着源代码目录的不断调整,项目的编译过程也发生了相应的变化。

    假设您有一个简单的 C 项目,共有三个源文件。按照我上面介绍的第一种目录组织方式,这些文件是组织在src中的,包含项目根目录下的文件夹。它们各自的内容如下图所示:

    其中,src/main.c文件是程序入口main函数所在的文件。src/mod.h和include/mod.c这两个文件共同为模块mod提供了对应的外部接口声明和具体实现。

    按照我们之前的习惯,通过下面的命令,我们可以使用GCC编译器来完成这个项目的编译过程。其中,我们指定了所有需要参与编译的.c文件。使用 -I 选项,我们为编译器指定在查找头文件时要搜索的目录,即“./include”。使用 -l 选项,程序在运行时所依赖的数学库可以在运行时顺利链接。

    gcc src/main.c src/mod.c -I./include -lm -o bin/main

    此时你可能会觉得编译多文件C应用程序只和单文件C应用程序一样,但编译涉及的源文件数量和命令中使用的配置项更多。

    但是,随着项目规模逐渐增大,这种编译方式将面临两个重要问题。首先是如何管理冗长的编译命令。这个问题关系到我们是否能够清楚的知道项目每次编译的具体状态,以及是否能够快速准确地相应修改这些配置项。

    其次,每次执行上述命令,只生成最终的二进制可执行文件,使得编译的中间结果无法得到有效利用。因此,每次修改后,代码都需要再次经过完整的编译过程。对于大型项目,这无疑降低了开发效率。

    有没有办法解决这两个问题?答案是肯定的。首先,让我们看看如何使用 Makefile 编译结构化 C 项目。

    使用 Makefile 进行结构化编译

    Makefile 是一种组织项目代码编译过程的方式解释型 编译型语言 跨平台性好,通常用于 ()类 Unix 操作系统,它通常与名为 make 的构建自动化工具结合使用。make 最初由贝尔实验室的 Stuart Feldman 于 1976 年实现,后来被集成到 Unix 系统中。

    make 执行时,会在当前目录中搜索一个名为 Makefile 的文本文件,并按照其中指定的一系列规则,有序地编译项目。

    比如上面提到的例子,我们可以写一段如下所示的文本,保存到项目根目录下一个名为 Makefile 的文件中。然后,通过直接在文件所在目录执行make命令,项目编译正确。

    bin/main: src/main.c src/mod.c

    gcc src/main.c src/mod.c -I./include -lm -o bin/main

    Makefile 使用类似于声明式编程语言的简化语法,让开发者可以灵活配置项目的编译过程。这里,上述配置文本的第一行指定了一个编译目标(bin/main),以及与该目标相关的依赖文件(src/main.c 和 src/mod.c)。后面所有用 Tab 键缩进的行(这里是第二行)用于配置从依赖文件到目标文件的编译转换的细节。如您所见,我们使用与之前完全相同的命令来实现此过程,但两者在编译时的差异逐渐变得明显。

    这样我们就部分解决了上面提到的问题,即每次修改代码后,直接运行编译命令造成的“全编译”,进而降低了开发效率。make 命令在每次实际构建之前跟踪每个构建目标及其依赖项的版本信息(通常是“最后修改时间”)。仅当自上次编译后相关依赖项的内容发生更改或目标文件不存在时,才会再次编译目标。这样,我们可以将大部分项目编译过程集中在必要的少数源文件上,而不会“浪费”其他已编译的中间目标文件。

    接下来,我们尝试进一步优化Makefile中的配置项,进一步将最终的二进制编译目标与各个中间依赖分开。而且,通过抽象出构建命令的可配置部分,我们还可以使整个构建脚本更具可读性和可用性。优化后的文件内容如下:

    # 自定义宏来控制编译细节;

    CC = gcc

    CFLAGS = -I./包括

    LDFLAGS=-lm

    TARGET_FILE = bin/main

    # 描述每个目标的详细编译步骤;

    $(TARGET_FILE): $(patsubst src/%.c,src/%.o,$(wildcard src/*.c))

    $(CC) $^ $(LDFLAGS) -o $@

    src/%.o: src/%.c 包含/%.h

    $(抄送) $

    可以看到,我们通过“#”开头的注释信息将整个Makefile的内容分成了两部分。

    第一部分包含一些可由用户配置的宏常量。在 make 运行时,这些宏将被替换为下面已配置的特定编译命令。这样,用户可以通过修改这些值,在一定范围内定制自己想要的编译过程。

    第二部分对应每个编译目标的具体编译细节。这里我们将初始编译命令拆分为以下两步:

    1.编译器编译src中同名的.c和.h文件,并将include文件夹放入对应的.o目标文件中;

    2.编译器一次编译所有 .o 文件并生成最终的二进制可执行文件。

    这样,我们添加了可重用的中间编译结果,使得通过make命令的每个编译过程都被限制在修改过的同名.c或.h文件中。这样就可以最大程度地实现“中间结果的复用”。

    为了帮助大家理解这部分配置代码,我整理了代码中大家可能不熟悉的Makefile语法元素的含义,放在下表中供大家参考:

    Makefile帮助我们解决了单个编译命令可读性低、中间结果复用性差等诸多问题。

    但是仔细观察会发现,我们在 Makefile 中使用的各种命令和参数选项都与程序当前运行的操作系统和平台直接相关。那么,将同一个 Makefile 复制到其他环境时它是否仍然有效?答案是“视情况而定”。但很明显,“Makefile + make”方法本身不能直接在(如)Unix以外的其他操作系统上使用。因此,如何进一步满足C项目的跨平台自动编译成为社区思考的另一个重要方向。接下来我们看看这个问题是如何解决的。

    使用 CMake 的跨平台自动化构建

    “抽象”往往是解决这类问题的法宝。为了保证项目编译脚本的可移植性,我们不能使用与具体软硬件实现相关的各种信息。因此,我们可以采取一种简单的方法:通过提供平台中立、中立的配置选项,“抽象”所有与项目构建相关的重要特性。并且,在项目实际编译之前,要根据目标平台的具体情况来构建项目。

    接下来给大家介绍的工具CMake(Cross-platform Make)就是这样实现的。不过相比直接编译代码,CMake会根据平台的具体情况生成相应的“平台本地构建工程”。比如在()类Unix系统上,会生成项目对应的Makefile;在 Windows 系统上,它会生成项目对应的 Visual Studio 项目文件。在此基础上,再利用平台上的相关工具,CMake就可以完成项目的真正搭建。

    与 Makefile 类似,CMake 指定描述项目编译细节的配置信息,也需要存储在名为 CMakeLists.txt 的文本文件中。作为对比,您可以使用如下所示的 CMake 配置来编译我们在本讲开始时介绍的 C 项目。每一行“代码”的具体作用,可以参考上面的注释进一步了解。

    cmake_minimum_required(版本 3.10)

    # 设置项目名称;

    项目(测试)

    # 设置二进制对象文件名;

    设置(TARGET_FILE“主”)

    # 添加源文件目录;

    aux_source_directory(./src DIR_SRCS)

    # 设置二进制对象文件的依赖关系;

    add_executable(${TARGET_FILE} ${DIR_SRCS})

    # 设置头文件搜索目录;

    target_include_directories(${TARGET_FILE} PUBLIC “${PROJECT_SOURCE_DIR}/include”)

    # 设置要链接的库;

    target_link_libraries(${TARGET_FILE} PUBLIC m)

    可以明显看出,相比Makefile,CMake的配置信息更加清晰易懂。例如,关于命令 target_include_directories 的具体用途,我们可以从它的名字中猜到。其实就是对应GCC编译器的-I参数,可以用来指定查找头文件时的查找目录。

    项目的CMakeLists.txt文件写好后,我们可以通过以下步骤完成项目的编译:

    1.使用命令 mkdir build && cd build 创建并进入一个临时目录,用于存放编译结果文件;

    2.使用命令 cmake .. 生成本地化的构建项目。在这里,CMake 将根据用户在 CMakeLists.txt 中指定的信息检查当前环境,包括编译器的 ABI、可用性和支持特性。检查结束后,CMake会根据检查结果动态生成一个本地化的构建项目,可用于支持项目在当前环境下的编译;

    3.使用命令 cmake –build 。让CMake使用本地相关工具完成项目的最终编译过程。

    其他可选工具

    事实上,早在 CMake 出现之前,类似的跨平台自动化构建工具 Autotools 工具集就已经出现在 GNU 之下。它们帮助我们将各种项目的源代码移植到许多不同的 Unix 系统上。但由于学习成本高、使用繁琐,正逐渐被CMake所取代。

    当然解释型 编译型语言 跨平台性好,除此之外,还有Meson、Tup、Bazel等构建工具供你选择。它们的使用方式都不同,您可以单击相应的链接了解更多信息。但从实际情况来看,功能齐全、社区和解决方案成熟的CMake无疑是C项目跨平台自动化构建的最佳选择。

    站内大部分资源收集于网络,若侵犯了您的合法权益,请联系我们删除!
    欧资源网 » 一聊:如何组织C项目的源代码目录结构与编译流程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    欧资源网
    一个高级程序员模板开发平台

    发表评论