欢迎光临散文网 会员登陆 & 注册

《Makefile 光学教程》之面向 Makefile 编程·Unit Test [CPL]

2023-09-19 15:49 作者:紧果呗  | 我要投稿

此教程将计划以两部分内容呈现,目标是从零基础到 GNU make 最本原的逻辑原理的掌握,这是第二部分内容,分按不同的工程类型分成多个示范项目来展示。零基础可以先看第一部分:Basic Concepts:

  1.  🐣 Basic Concepts

  2.  🐣 Demo Projects

    1. 🐣 Scheme R6RS 语言规范文档处理 [LaTeX]

    2. 🐣 Multi threaded Download [Msys2 Packages]

    3. 🐣 C/C++ Project Templates [GLib Gobject]

    4. 🐣 Erlang Project Templates

    5. 🐣 Unit Test [CPL]

完整《Makefile 光学教程》以及 GNU M4 教程参考开源文档:https://github.com/Jeangowhy/opendocs/blob/main/Makefile.md


为了在 Make 脚本中测试程序,需要了解各种 shell 环境中如何使用 Exit Code:


1. Windows 传统命令行 CMD 使用 `echo %errorlevel%` 打印退出码,不等于 0 就是错误退出; 

2. PowerShell 使用 $LASTEXITCODE 获取退出码,或者 $? 获取布尔值,True 表示正常退出;

3. Bash 使用 $? 自动变量,比如 `echo $?` 显示 127 表示 False,程序错误退出;


潜在的问题:Msys2 MinGW 编译的程序出现非法指针时,bash 不能检测到返回码,-1073741819 0xC0000005 STATUS_ACCESS_VIOLATION。2.3.1 NTSTATUS Values。


使用 Msys2 没有正确安装编译器版本,或者安装在错误的平台目录下,也可能导致编译出来的程序出现内存违规访问。因此,要确保编译器正常工作,编译生成的程序能够正常执行。


注意:虽然反斜杠 \ 符号在 Makefile 中并不是功能符号,它和其它一般字符一样对待,但是构建命令执行时,将它输入到 shell 中执行就会有转义字符的功能。所以,使用 make 命令时一般需要在 Makefile 当前目录下执行,如果不是,将使用斜杠 / 作为 Makefile 路径的目录分隔符。


另外,更重要的是不能使用 ifeq 或者 ifneq 对被程序的输出结果进行判断。因为条件指令是立即绑定模式,不能使用自动变量,或者 eval 函数设置的变量,只有 Makefile 中已有定义的全局变量才可以使用。


并且,内置函数 if 只能做字符串是否为空值的判断,不能做等值比较。Make v4.4 版本引入的 intcmp 函数才能比较数值的小于、等于、大于三种状态。所以判断结果是否相等,可以交给 shell 命令去判断。

Make 还有一个做等值判断的 “诡计”是使用做局部匹配的 findstring 函数,其逻辑是:如果一个字符串 A 包含另一个字符串 B,并且这个 B 又包含 A,那么它们相等。我想可以发掘一些 GNU Make 没有直接提供,并且又可以通过组合各种逻辑实现的功能,大概是这个构建工具在功能实现上如此克制的原因吧。为此,这些专用的功能函数,可以使用专用脚本“保管”集中管理,需要引用就通过 include 指令加载,以下是 utilities.mk 函数库参考:



以下是用于构建待测试程序,以及进行测试的脚本,功能说明如下:


1. 测试程序 type.c 代码文件和 utilities.mk 函数库与 Makefile 共同存放于 src 目录;

2. 在任意目录中执行 make -f Makefile 进行编译、测试,输出存放于上一级 bin 目录;

3. 使用 GCC 编译器,DEBUG、RELEASE 两套基本配置,使用 make DEBUG=true 激活调试配置;

4. 配置了一条 test_hello 用于测试的规则,配合 EXPACT PASS FAILED 等变量输出相应测试信息;

Build it thenTest It


为了让编译与测试更加自动化,可以使用 Node watch 模块实时监视并执行命令:


Deno 也内置了 watch 功能,也可以直接通过命令行运行,但是它毕竟不是专用的 watch 工具,可以将以下功能编写到 js 或 ts 脚本文件中再执行,并且可以创造出一些自定义功能:


以下是作为一个要测试的使用的目标程序:



字节序 Endianness 是在处理多字节的数据时,不同的 CPU 构架使用不同的方式。


比如 0x1234 这个值(十进制 4660):



- Big endian 方式,PowerPC 或苹果 CPU 构架使用。

- Little endian 方式,Intel x86 系统使用。


测试程序中,使用“123”字符串作为一个测试数据,由于 C 语言中的字符串使用 \0 作为结束标志,即 null-terminated string 格式。在一个四字节的整形数值中,其中三个字节位置设置为 “123”,最后一个字节需要设置为 null,否则打印函数就会因为边界失误面导出内存违规访问。通常,这个违规并不一定会发生,因为内存中有很多 null 值的位置,随意遇到一个 null 就可以终结这个字符串。但是,从逻辑上这就是违规的内存访问。


字节内部的比特位也有两种序,Most Significant Bit (MSB),在二进制数中属于最高有效位,MSB 是最高加权位。Least Significant Bit (LSB) 在二进制数中意为最低有效位。一般来说,按书写习惯,MSB 位于二进制数的最左侧,LSB 位于二进制数的最右侧。


CPU 存储数据操作的最小单位是一个字节,至于字节内部的比特序如何,对于程序来说是一个黑盒子。



使用 C 语言定义结构体或联合体时,实践中通常和 typedef 一起使用,这样方便定义结构体类型的变量。但是,至今我仍记得结构体定义与 typedef 关键字使用时出现的混乱状态,让我毕生难忘。总得来说,定义一个结构体类型和实例化结构体对象,它们有部分语法结构会因为 typedef 关键字的使用而出现重叠,这也是混乱的主要来源。


假设要定义一个 ByteChunk 结构体,以及其实体变量 byteChunk,各种形式如下,当然还可以使用花括号对实例进行初始化:



《Makefile 光学教程》之面向 Makefile 编程·Unit Test [CPL]的评论 (共 条)

分享到微博请遵守国家法律