# ctool **Repository Path**: Lamdonn/ctool ## Basic Information - **Project Name**: ctool - **Description**: 常用的C语言工具 - **Primary Language**: C - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2023-02-22 - **Last Updated**: 2025-07-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ctool ## 介绍 常用的C语言工具 ## 软件架构 软件架构说明 ## 工具清单 - [x] 不定参数 - [x] 日期 - [x] 哈希 - [ ] 加解密 `雏形` - [x] 常用宏定义 - [x] 内存使用测试 - [x] 数学库 `未完善` - [x] 字符串命令 - [ ] crc校验 ## 使用说明 ### 不定参数 #### 场景 C/C++有很多场景需要用到不定参数,比如最常用的就是`printf`函数,后面可以按格式跟着若干个不定参数。不定参数在函数中形参用`...`来表示,然后在函数体用`va_start`、`va_arg`、`va_end`这三个宏定义方法来承接不定参数,但是在承接不定参数时候,要`承接多少个`呢?这个通常的方法是给函数传不定参数时候也传入不定参数个数。例子如下: ```c int adds(int num, ...) { int sum = 0; va_list args; va_start(args, num); while (num--) sum += va_arg(args, int); va_end(args); return sum; } int main(int argc, char *argv[]) { printf("sum = %d\r\n", adds(3, 1,2,3)); return 0; } ``` 结果: ``` sum = 6 ``` 但是这里面,如果传入`num`个数和实际传入不定参数个数不一样,那么就会得不到预期的结果了。那么有什么方法是可以获取不定参数个数来保证传入的`num`和实际的不定参数个数一致的呢,这就是本文要介绍的获取不定参数的方法。 #### 获取不定参数个数 先来看效果,在前面的基础上加多宏定义 ```c int adds(int num, ...) { int sum = 0; va_list args; va_start(args, num); while (num--) sum += va_arg(args, int); va_end(args); return sum; } #define add(...) adds(ARGC(__VA_ARGS__), __VA_ARGS__) int main(int argc, char *argv[]) { printf("sum = %d\r\n", add(1,2,3)); printf("sum = %d\r\n", add(1,2,3,4)); printf("sum = %d\r\n", add(1,2,3,4,5)); return 0; } ``` 结果 ```c sum = 6 sum = 10 sum = 15 ``` 可以看到,通过宏定义,不需要再手动传入宏定义参数个数了,那么这个`ARGC`宏定义写了啥,先附上简短的代码 ```c #include #ifndef ARG_MAX #define __ARGS(X) (X) #define __ARGC_N(_0,_1,_2,_3,_4,_5,_6,_7,N,...) N #define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,8,7,6,5,4,3,2,1)) #define __ARG0(_0,...) _0 #define __ARG1(_0,_1,...) _1 #define __ARG2(_0,_1,_2,...) _2 #define __ARG3(_0,_1,_2,_3,...) _3 #define __ARG4(_0,_1,_2,_3,_4,...) _4 #define __ARG5(_0,_1,_2,_3,_4,_5,...) _5 #define __ARG6(_0,_1,_2,_3,_4,_5,_6,...) _6 #define __ARG7(_0,_1,_2,_3,_4,_5,_6,_7,...) _7 #define __VA0(...) __ARGS(__ARG0(__VA_ARGS__,0)+0) #define __VA1(...) __ARGS(__ARG1(__VA_ARGS__,0,0)) #define __VA2(...) __ARGS(__ARG2(__VA_ARGS__,0,0,0)) #define __VA3(...) __ARGS(__ARG3(__VA_ARGS__,0,0,0,0)) #define __VA4(...) __ARGS(__ARG4(__VA_ARGS__,0,0,0,0,0)) #define __VA5(...) __ARGS(__ARG5(__VA_ARGS__,0,0,0,0,0,0)) #define __VA6(...) __ARGS(__ARG6(__VA_ARGS__,0,0,0,0,0,0,0)) #define __VA7(...) __ARGS(__ARG7(__VA_ARGS__,0,0,0,0,0,0,0,0)) #define ARG_MAX 8 #define ARGC(...) __ARGC(__VA_ARGS__) #define ARGS(x, ...) __VA##x(__VA_ARGS__) #endif ``` 在使用上,直接关注`ARG_MAX`、`ARGC(...)`、`ARGS(x, ...)`这三个即可。 那么`ARGC(...)`是怎么做到获取到不定参数个数的呢,以传入三个参数的为例,依次展开。 ``` ARGC(a, b, c) // 第一步展开 __ARGC(a, b, c) // 换了个名字 (__ARGC_N(__VA_ARGS__,8,7,6,5,4,3,2,1)) // 由__ARGS(X) (X)展开,多了用个括号包住 (__ARGC_N(a,b,c,8,7,6,5,4,3,2,1)) 展开到这里,按位置对应参数,a,b,c参数分别对应到了_0,_1,_2的位置,当__ARGC_N宏定义取N号位置的参数时,就取到了3,也就是3个不定参数。 (a, b, c, 8, 7, 6, 5, 4, 3, 2, 1) | | | | | | | | | | | | | | | | | | (_0,_1, _2, _3, _4, _5, _6, _7, N, ...) 也就是得到最终的展开为 ARGC(a, b, c) ===>>> (3) 同样比如传入4个不定参数 (a, b, c, d, 8, 7, 6, 5, 4, 3, 2, 1) | | | | | | | | | | | | | | | | | | (_0,_1, _2, _3, _4, _5, _6, _7, N, ...) 得到最终的展开为 ARGC(a, b, c, d) ===>>> (4) ``` 但是,看到这里估计都会发现这里里面存在一个问题,当**不定参数个数大于8**了,展开是怎么样子的呢? #### 修改不定参数的个数上限 接着前面的问题,当参数大于8了,比如9个,展开是怎么样的呢? ``` 传入9个不定参数 (a, b, c, d, e, f, g, h, i, 8, 7, 6, 5, 4, 3, 2, 1) | | | | | | | | | | | | | | | | | | (_0,_1, _2, _3, _4, _5, _6, _7, N, ...) 得到最终的展开为 ARGC(a, b, c, d, e, f, g, h, i) ===>>> (i) ``` 很明显大于8的参数个数是获取到了**i**,而不是我们所期待的**9**,因为这里能看到宏展开后最大也就是只能8,同理,该怎么增大这个上限,应该都能猜到怎么增大上限了。 **别着急**!不用自己手动去写,多麻烦,代码的生成函数我已经写出来了(在文末),直接copy这个函数,传入max就可以输出相应代码了。 一般来说,函数的参数不宜太多,参数太多压栈会影响函数执行效率也会降低代码可读性,一般超过8个参数就得考虑重写函数了。(C89最多31个形参,C99最多127个形参,没有验证过),这里是获取参数个数上限**124**的参考代码。 [**代码链接**](https://gitee.com/Lamdonn/ctool/blob/master/src/arg.h) #### 不定参数个数优化 宏定义展开直接获取`N`的话会存在一个问题,不传入参数,也是返回不定参数个数1,也就是 ``` ARGC() ===>>> (1) ``` 这是为什么呢?宏定义展开一下 ``` 传入9个不定参数 ( , 8, 7, 6, 5, 4, 3, 2, 1) | | | | | | | | | | | | | | | | | | (_0,_1, _2, _3, _4, _5, _6, _7, N, ...) 得到最终的展开为 ARGC() ===>>> (1) ``` 这里不传入参数,`_0`也会占着空位置,也就是等同1个参数了,所以最终也是获得1。 那有什么解决办法吗? ```c #define __ARGC_N(_0,_1,_2,_3,_4,_5,_6,_7,N,...) N ``` 修改宏定义,把直接获取`N`换成`N==1?(#_0)[0]!=0:N`,这个是什么意思,再展开一下就得到N被换成了1 ``` ARGC() ===>>> (1==1?("")[0]!=0:1) ``` 就是把第0个参数的名字转成字符,当N==1的时候,判断是不是空字符串,是空字符串就表明没传入参数,就返回0,否则就N了。**(这里`(#_0)`转成字符串需要浪费空间)** #### 获取不定参数的指定参数 如同前面,当把宏定义获取N换成获取其他位置,就可以获取对应位置的参数了。 以获取1号位置参数为例,宏定义展开结果。 ``` (a, b, c, 0, 0) | | | | | | | | | | (_0,_1, ...) _1 也就是得到最终的展开为 __VA1(a, b, c) ===>>> (b) 再配合 #define ARGS(x, ...) __VA##x(__VA_ARGS__) 最终 ARGS(1, a,b,c) ===>>> (b) ``` 其他位置的参数展开也同理。 #### 代码生成函数 ```c // max为参数个数的上限,allow_0为时候支持获取空参数个数 int arg_generate(int max, int allow_0) { int n, i; if (max <= 1) return 0; printf("#include \r\n"); printf("\r\n"); printf("#ifndef ARG_MAX\r\n"); printf("\r\n"); printf("#define __ARGS(X) (X)\r\n"); printf("\r\n"); printf("#define __ARGC_N("); for (n = 0; n < max; n++) { printf("_%d,", n); } printf("N,...) %s\r\n", allow_0?"N==1?(#_0)[0]!=0:N":"N"); printf("\r\n"); printf("#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__"); for (n = 0; n < max; n++) { printf(",%d", max - n); } printf("))\r\n"); printf("\r\n"); for (n = 0; n < max; n++) { printf("#define __ARG%d(", n); for (i = 0; i <= n; i++) { printf("_%d,", i); } printf("...) _%d\r\n", n); } printf("\r\n"); for (n = 0; n < max; n++) { printf("#define __VA%d(...) __ARGS(__ARG%d(__VA_ARGS__", n, n); for (i = 0; i <= n; i++) { printf(",0"); } printf(")%s)\r\n", n?"":"+0"); } printf("\r\n"); printf("#define ARG_MAX %d\r\n", max); printf("#define ARGC(...) __ARGC(__VA_ARGS__)\r\n"); printf("#define ARGS(x, ...) __VA##x(__VA_ARGS__)\r\n"); printf("\r\n"); printf("#endif\r\n"); return 1; } ``` #### 小结 用`int arg_generate(int max, int allow_0)`函数生成代码,直接使用`ARG_MAX`、`ARGC(...)`、`ARGS(x, ...)`这三个即可。 ### 日期 #### 说明 简单的计算万年历的工具,可以方便的设置日期(1900.01.01为参考基准)和获取日期,以及方便获取日期偏移。 #### 方法 ```c int set_date(date *date, unsigned short year, unsigned char month, unsigned char day); int update_date(date *date, int offset); #define get_year(d) ((d).year) #define get_month(d) ((d).month) #define get_day(d) ((d).day) #define get_week(d) ((d).count%7) ``` 一共也就这几个简单的方法,设置、更新、获取。 #### 例子 ```c int main(int argc, char *argv[]) { date d; set_date(&d, 2008, 8, 8); // 设置日期为2018.08.08 update_date(&d, 1000); // 日期往后1000天 printf("%04d.%02d.%02d week-%d\r\n", get_year(d), get_month(d), get_day(d), get_week(d)); return 0; } ``` 结果,得到2018.08.08的1000天后为2011.05.05,星期四 ``` 2011.05.05 week-4 ``` ### 哈希 #### 说明 常用的哈希函数,32位哈希 #### 方法 ```c unsigned int hash_bkdr(void *data, int size); unsigned int hash_ap(void *data, int size); unsigned int hash_djb(void *data, int size); unsigned int hash_js(void *data, int size); unsigned int hash_rs(void *data, int size); unsigned int hash_sdbm(void *data, int size); unsigned int hash_pjw(void *data, int size); unsigned int hash_elf(void *data, int size); unsigned int hash_dek(void *data, int size); unsigned int hash_bp(void *data, int size); unsigned int hash_fnv(void *data, int size); unsigned int hash_jdk6(void *data, int size); ``` ### 加解密 ### 常用宏定义 #### 说明 通用型的常用宏定义 ### 内存使用测试 #### 说明 在C语言内存里面,内存分配一般有编译器分配和程序员分配。 编译器分配内存为最常见的方式,编译器自动分配内存并在结束使用后自动释放,无需其他干预。而程序员内存分配,也就是动态分配,由程序员分配不定长的内存空间,并且在结束使用后也需要去手动释放。在这里,如果程序员忘记手动释放,则会造成空间的浪费,甚至会占满内存。 这里,介绍一种可以检查内存使用情况的小工具`valloc`。用其方法代替的内存分配方法,可以计算动态内存使用的情况。 先看个简单的例子 #### 例子 ```c #include #include int main(int argc, char *argv[]) { malloc(12); return 0; } ``` 像上面这种情况,我们分配了内存,如果不释放代码一复杂就很难发现内存没有释放。 加入`valloc`工具 ```c #include #include #include "src/valloc.h" int main(int argc, char *argv[]) { int area = 0, use = 0; malloc(12); v_mcheck(&area, &use); if (area || use) { printf("|||----------->>> area = %d, size = %d\r\n", area, use); } return 0; } ``` 添加`valloc`头文件,该头文件宏定义会覆盖替代`malloc`、`free`等函数,从而不用改动其他代码,然后在结束的地方调用`v_mcheck`方法检查动态内存使用情况。 结果,没有释放的内存就报出来了,有一片空间没释放用12大小的内存。 ``` |||----------->>> area = 1, size = 12 ``` #### 原理 比如分配 num 大小的空间 | <-- sizeof(int) ----> | <------- num -------> | | _____________________ | _____________________ | 1. 调用原版`malloc`函数分配`sizeof(int)+num`大小的空间 2. 前面`sizeof(int)`大小的空间作为`int`型记录该片空间的大小`num` 3. 增加记录分配次数到模块静态变量`count`,增加记录分配空间大小到模块静态变`use` 4. 返回原分配地址向后偏移`sizeof(int)`后的地址给用户作为新的分配地址 同样,在释放内存时候 1. 先把释放的地址向前偏移`sizeof(int)`得到实际的分配地址 2. 实际地址的前`sizeof(int)`空间记录了该片空间的大小,模块静态变量`count`和`use`相应缩减 3. 调用原版`free`函数释放实际的分配地址 然后,调用`int v_mcheck(int *_count, int *_use);`方法获取模块静态变量`count`和`use`即可得知动态内存的使用情况了 在头文件中增加以下的宏定义 ```c #define malloc v_malloc #define calloc v_calloc #define free v_free #define realloc v_realloc ``` 在需要统计动态内存的源文件文件中,只需添加`valloc`头文件进来,原版的`malloc`、`free`等函数则会被同名宏定义替换成`v_malloc`、`v_free`等带`v_`前缀的函数,而无需改动其他代码。 #### 小结 在需要测量动态内存使用的文件中简单的包含头文件而无需改动其他代码,然后在需要测量内存使用的地方调用`v_mcheck`方法检查动态内存使用情况。 ### 数学库 常用的数学库 ### 字符串命令 #### 说明 这是个简单的命令行工具,类似在控制台输入命令行,然后执行相应的操作。 前后一共就三个操作方法,执行命令、导出命令、清空命令。 #### 操作方法 ```c int command(const char *line); // 执行命令 int command_export(const char *name, int (*handle)(int argc, char *argv[])); // 导出命令 void command_clear(void); // 清空命令 ``` 执行命令,命令格式为字符串格式,中间以空格分开为参数,第0个参数为命令类型的参数。 导出命令,命令包含命令的名字和对应需要执行的回调函数。回调函数形参分别是参数个数以及参数列表,返回值建议0为成功,正值为其他,不使用负数。 清空命令,清空所有的命令。 #### 例子 ```c int test(int argc, char *argv[]) { int i; printf("test function! %d\r\n", argc); for (i = 0; i < argc; i++) { printf("%s\r\n", argv[i]); } return 0; } int name(int argc, char *argv[]) { int i; printf("name function! %d\r\n", argc); for (i = 0; i < argc; i++) { printf("%s\r\n", argv[i]); } return 0; } int main(int argc, char *argv[]) { char input[64]; command_export("test", test); // 导出命令 command_export("name", name); // 导出命令 scanf("%[^\n]", input); // 接收全部输入字符 command(input); // 执行输入的命令 command_clear(); // 清空命令 return 0; } ``` 该例子定义了两个回调函数,都是分别输出命令参数,通过接收控制台输入来处理命令。 分别在控制台输入以下测试命令。 * 命令 ``` test 1 2 3 4 ``` * 结果 ``` test function! 5 test 1 2 3 4 ``` * 命令 ``` name ZhangSan LiSi ``` * 结果 ``` name function! 3 name ZhangSan LiSi ``` ### crc校验