C语言位域:把变量拆成二进制位看
调试的时候经常想看一个变量在内存里的二进制到底长什么样——特别是学 float 的 IEEE 754 表示、研究位运算的时候。这个需求用 C 语言的**位域(bit field)+ 联合体(union)**就能优雅解决,我做成了一个测试库并开源: GitHub:https://github.com/COSMICAL-CONTAINER/C_Program-Test.h 思路:union + 位域核心是一对搭档: union:让同一块内存既能按整体读写(all),又能按位读写(decode) 位域:把一个字节拆成 8 个 1 bit 的成员 char 的拆解结构: 12345678910111213typedef struct _data_char_structTypDef{ unsigned char bit0 : 1; unsigned char bit1 : 1; // ... bit2 ~ bit6 unsigned char bit7 : 1;} data_char_structTypeDef;typedef union...
统计 __VA_ARGS__ 参数个数
可变参数宏 __VA_ARGS__ 有个著名的难题:怎么知道参数有几个?预处理器没有循环、没有计数,但利用”占位符平移”技巧可以完美解决。 技巧原理123#define _PrintMacroArgCount(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,XX,...) XX#define PrintMacroArgCount(...) \ _PrintMacroArgCount("ignored", ##__VA_ARGS__,9,8,7,6,5,4,3,2,1,0) 关键在参数的错位对齐: 传 3 个参数时,展开成 _PrintMacroArgCount("ignored", 1, 2, 3, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) _0 吃掉 “ignored”(占位符),_1~`_3 吃掉真实参数,XX` 恰好落在 3 上 传 0 个参数时,XX 落在 0 上,顺便解决了 __VA_ARGS__ 为空时的逗号问题(##__VA_ARGS__ 会吃掉多余的逗号) 应...
任意类型的栈:C 泛型容器的两条路线
教科书上的栈长这样:int stack[MAX]——只能装 int。2022 年 10 月我刚学完数据结构,决定写一个”任意类型都能装”的栈;2024 年 7 月翻出来重写了一遍。两个版本放在一起看特别有意思:一个用 memcpy 把数据拷进容器,一个只存数据的地址——这正是 C 语言里实现泛型容器的两条路线。后来写的通用链表 glist 和计算器的栈,都能在这两条路线里找到自己的位置。 📌 更新(2026-08-31):为写这篇重新编译复现了两个版本,抓到了 2022 版里的两个老 bug(见复盘一节),实测输出均已录入正文。 2022 版:void* 加 typesize,memcpy 说了算这个版本的完整设计思路,被当年的我用一段英文注释写在了 stack.h 的开头——现在读来简直就是一篇给自己看的教案: 1234567891011121314151617181920// This stack is implemented by void pointer, so it is GENERIC.//// How the void pointer allows assig...
Print.h 开发记(六)V1.6
系列导航:上一篇:Print.h 开发记(五)V1.5 打印库成熟之后,最先”吃自己狗粮”的场景就是调试。V1.6 加入了一整套 debug 宏。 debug:打印变量名和它的值调试时最常写的就是 printf("x = %d\n", x)——变量名要手打两遍。# 字符串化运算符可以让宏自动完成: 12#define debug_valuename_to_str(value) #value#define debug(obj) print(LIGHT_GREEN("debug: "), debug_valuename_to_str(obj) " = ", obj, "\n") debug(count) 展开后自动输出 debug: count = 10,变量改名也不用同步。 WARN_IF / ERR_IF / ERR_EXIT_IF三个按条件触发打印的宏,全部用 do { } while(0) 包裹——这是宏定义多语句的标准写法,保证在 if/else 里展开不出问...
给 printf 添加自定义格式符
printf 的 %d、%f 是写死的,想直接打印自己定义的结构体怎么办?比如复数 Imaginary、分数 Fraction。实验了一个方案:给 printf 加上自定义格式符,注册之后就能写 %I 直接打印复数。 背景:rt_printf 是什么实验基于 RT-Thread(RTT,国产实时操作系统) 的打印实现 rt_printf 改造——接口和标准 printf 一致,但更轻量,是嵌入式圈里常用的东西。项目的文件结构: rt_printf.c / rt_printf.h:打印核心(本次改造的对象) mprintf_argc.h:自定义格式符的注册宏(__mprint_argc__) main.c:使用示例 用法123456789101112131415161718typedef struct Imaginary{ float real; float image;} ImaginaryTypedef;// 注册:'I' 这个格式符由 Imaginary2str 函数负责打印__mprint_argc__('I...
Print.h 开发记(五)V1.5:覆盖基础类型
羡慕 Python 的 print() 吗?这个系列记录我用纯 C 实现一个万能 print 库的全过程——项目已开源: GitHub:https://github.com/COSMICAL-CONTAINER/C_Program-Print.h V1.5 是一次”夯实基础”的版本,没有新花样,主要做了三件事:补全基础类型与指针类型的打印、规范缓冲区与命名、完善注释。库从这个版本开始有了”正式出品”的样子。 基础类型覆盖:分发表补全_Generic 分发表补上了缺的三种基础类型——unsigned char、unsigned int、long long,各自对应一个新的打印函数: 1234567891011121314void print_uchar(unsigned char num){ printf("%c", num);}void print_uint(unsigned int num){ printf("%u", num);}void print_longlong(long l...
Print.h 开发记(四)V1.4
系列导航:上一篇:Print.h 开发记(三)V1.3 V1.4 是 print 库里程碑式的一版:print 和 println 从”一次只能打印一个”升级为最多支持 10 个参数——这就是 C 语言版的”重载”。 用法12print(1, 2.5, "hello"); // 一次打印三个不同类型println(num, str, 3.14); // 多参数 + 自动换行 核心机制:参数计数宏之前单独写过一篇统计可变宏参数个数,讲的就是”占位符平移”技巧——先数出参数个数: 12#define PrintMacroArgCount(...) _PrintMacroArgCount(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)#define _PrintMacroArgCount(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, COUNT, ...) COUNT 真实参数会把后面的 10,9,8…往右挤,COUNT 落在几就代表有几个参数。 拼接...
Print.h 开发记(三)V1.3
系列导航:上一篇:Print.h 开发记(二)V1.2 V1.3 的更新只有一个主题,但很关键:颜色打印宏的输入格式拓展——现在既可以直接传字符串字面量,也可以传变量了。 V1.2 的限制V1.2 的颜色宏内部是字符串拼接: 1#define RED(str) "\033[0;32;31m"NONE(str) NONE(str) 是 str "\033[m"——相邻字符串字面量的编译期拼接。这意味着 RED("hello") 没问题,但 RED(some_var) 直接编译报错:变量不是字符串字面量,没法参与拼接。 V1.3 的解决新版本把”上色”和”打印”拆开:宏只负责生成转义序列与内容的组合,内容由 printf 的 %s 输出——变量自然也就支持了。同时引入 MAXSTRLEN 100 限制单段彩色文本的最大长度,内部用缓冲区拼接,避免溢出。 顺带的修正这一版还规范了头文件:加入了 MAXSTRLEN 的定义、整理了颜色宏的排版。版本日志里那句”拓展颜色打印宏,现在可以支持输入字符串或变量了”说的就是这个。 ...
Print.h 开发记(二)V1.2
系列导航:上一篇:Print.h 开发记(一)V1.0 V1.2 是第一个”补课”版本:修掉 V1.0 遗留的 bug,同时一口气加入了好几个打印能力。 本版更新 修复 V1.0 遗留的 bug 加入 unsigned long long 的打印格式 加入打印数组功能,可以自选数组中每两个元素的打印格式 加入不支持的格式错误处理 加入颜色打印代码 无符号长长整型1void print_ulonglong(unsigned long long num) { printf("%llu", num); } %llu 对应 64 位无符号整型,配合 _Generic 分发表新增一个分支即可。 数组打印数组打印支持自定义元素之间的分隔方式,一共三种:空格隔开、换行隔开、或者”下标 + 值”逐行打印,由调用方传一个模式常量决定——打印 int 数组和打印字符串数组都能得到好看的结果: 123#define printArrTypeSpace 0 // 元素之间用空格隔开#define printArrTypeln 1 //...
Print.h 开发记(一)V1.0
羡慕 Python 的 print() 吗?任意类型丢进去直接打印,不用记 %d、%f、%s。这个系列记录我用纯 C 实现一个万能 print 库的全过程——项目已开源: GitHub:https://github.com/COSMICAL-CONTAINER/C_Program-Print.h 这个系列会用一篇文章讲一个版本,从 V1.0 一直写到最新的 V1.8。本篇从 V1.0 讲起:它是怎么一步步搭起来的。 V1.0 第一步:print.h 宏分发C11 的 _Generic 是编译期的类型选择器:它不对表达式求值,只在编译阶段看一眼类型,然后”挑选”对应的答案——配合宏使用刚好零开销: 12_Generic(1, int: 10, double: 20, default: 0) // 整个表达式的值是 10_Generic(1.5, int: 10, double: 20, default: 0) // 整个表达式的值是 20 有了它,思路就通了:对 char 调 print_char,对 int 调 print_int……把它包进宏里,就能根据传入类...
