C 的一种易调试的宏用法, 以做代码模板

1 C 的一种易调试的宏用法, 以做代码模板

用 C 比较多时, utils 库其实是不缺的, 但要熟悉起来, 编码才会高效. 前一阵子尝试写哈稀表, 承蒙标准云张教主找来一个哈稀表的评测, 里面有些简易实现 -— 是的, 我只看简单的东西. 看完之后, 我站在别人的脚趾头上再实现了几个.

哈稀表实现有开放寻址和链表, 链表实现容易, 但时间和空间开销都较开放寻址 浪费. 做成高效通用的哈稀表, 有时, 多余函数调用的开销也是关键的, 宏和 inline 函数便在此处出现.

inline 函数很好, 不好的地方是类型检查上不绕开不方便, 绕开不安全. 但, 要 函数指针的形式, 便 inline 不得了. 最后一个考虑是宏了.

``该死的宏.''

宏不方便调试. 在写的时候要将几百行串成一行, 编译运行, 可能只告诉你宏调 用那一行有错, 这时要去那几百行慢慢找.

为此, 我看过 CPP 的文档, 失望而归. 然后, 在考虑写软件包自动编译安装脚本时, 发现 M4.

在 M4 之前, 我是想用 sed 来做这事的, 最后还是决定尝试一下不熟悉的 M4.

首先, 如常写 C 头文件代码(设为文件 ht.h.m4), 只是按一种约定, 一种类型的 操作给个共同的函数, 类型名前缀(这不算太坏的编码的规范, 如果不算好), 需 要做为泛型参数的给个名字. 最终, 我期望用 M4 处理一下这文件, 得到, 比如, 哈稀表的宏实现

HT_INIT(name, key_t, val_t, key_hash, key_equal).

其中, name 为结构名, 所有生成的函数结构将都以 name_ 开头. 如 key_hash 原型为

integer key_hash(key_t key)

具体可以是宏, 也可以是函数.

在测试时, 我只要定义好 key_t, val_t, key_hash, key_equal, 再包含头文件 (假设为 ht.h.m4), 就可以测试调用函数, 如

val_t name_get(name_t *p, key_t k).

测试完后, 可以用 m4 -P ht.h.m4 > ht.h 来生成头文件 ht.h, 里面有期望的 HT_INIT 宏. 在用 make 时, 可以添加规则.

%.h: %.h.m4
m4 -I$(srcdir) -P $^ > $(notdir $@)

%.c: %.c.m4
m4 -I$(srcdir) -P $^ > $(notdir $@)

挂羊头, 卖狗肉, 下面给另一个例子.

2 实例

CSEQ, 一个 C 的 sequnce 的实现.

2.1 文件 cpptemps.m4

文件内容如下段, 抽出来, 可以复用.

m4_divert(-1)
m4_define(`CS_QUOTE', `m4_ifelse(`$#', `0', `', ``$*'')')
m4_define(`CS_FOREACH', `m4_pushdef(`$1')_CS_FOREACH($@)m4_popdef(`$1')')
m4_define(`_CS_ARG1', `$1')
m4_define(`_CS_FOREACH', `m4_ifelse(`$2', `()', `',
`m4_define(`$1', _CS_ARG1$2)$3`'$0(`$1',(m4_shift$2), `$3')')')
m4_define(`CS_NO_ARGS',`_CS_NO_ARGS(`$1',`$2', `$'`#', `$'`@')')
m4_define(`_CS_NO_ARGS',`m4_pushdef(`$1',`$2`'m4_ifelse($3,0,,`('`$4'`)')')')
m4_define(`CS_APPEND_BACK_SLASH',`m4_patsubst(`$*',`
.',`\\\&')')
m4_define(`CS_LSTRIP',`m4_patsubst(`$*',`^[
]*')')
m4_define(`CS_DELETE_COMMENT_LINES',`m4_patsubst(`$*',`//[^
]*')')
m4_define(`CS_CASCAT',`CS_APPEND_BACK_SLASH(CS_LSTRIP(CS_DELETE_COMMENT_LINES($@)))')
m4_divert`'m4_dnl

2.2 文件 cseq.h.m4

实现文件, 主题无关的不优雅代码, 所以不全. 其中 defs.h 文件是一些包装内存分配等常见宏的定义.

CSEQ_FUNCS 里都是函数名, 如 size 对应 name_size, 在 CSEQ_INIT(cvs, char) 后, 将生成 cvs_size 函数.

///redundant comment introduced by m4`'m4_ifdef(`CS_FILE_CSEQ_H_M4',`',`m4_define(`CS_FILE_CSEQ_H_M4',`1')
#ifndef FILE_CSEQ_H__
#define FILE_CSEQ_H__ 1

#include "defs.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

__attribute__((__const__))
static inline size_t cseq_cal_resize(size_t n) {
if (n > 134217728UL) {
return n + (n >> 1);
}
return n + n;
}

///m4_ifelse(
/*)
m4_include(cpptemps.m4)
m4_define(`CSEQ_INIT', `m4_ifelse(
CS_NO_ARGS(`name_t', ``$1''`_t')
CS_NO_ARGS(`val_t', ``$2'')
m4_ifelse(m4_eval($# < 3),1,
`CS_NO_ARGS(`CSEQ_FUNC_ATTR', `static inline')',
`CS_NO_ARGS(`CSEQ_FUNC_ATTR', `$3')'
)
m4_pushdef(`CSEQ_FUNCS', (append, array, check_capacity,
free, get, init, new, prepend, realloc, release,
reverse, rmhead, rmtail, set, size, resize, splice, move__,
add, copy, detach, attach))
CS_FOREACH(`x', CSEQ_FUNCS, `CS_NO_ARGS(`name_'x, ``$1_''``''x)')
)m4_dnl*/

typedef struct {
val_t *base;
size_t capacity;
size_t size;
int start;
} name_t;

CSEQ_FUNC_ATTR void name_init(name_t *p) {
memset(p, 0, sizeof(*p));
}

CSEQ_FUNC_ATTR void name_release(name_t *p) {
xfree(p->base);
memset(p, 0, sizeof(*p));
}

CSEQ_FUNC_ATTR name_t* name_new() {
name_t *p = xmalloc(sizeof(name_t));
name_init(p);
return p;
}

CSEQ_FUNC_ATTR void name_free(name_t *p) {
name_release(p);
xfree(p);
}

CSEQ_FUNC_ATTR size_t name_size(name_t *p) {
return p->size;
}

// more bulk such as splice ...

///m4_ifelse(
///m4_popdef(`name_t')
///m4_popdef(`val_t')
///CS_FOREACH(`x', CSEQ_FUNCS, `m4_popdef(`name_'x)')
///m4_popdef(`CSEQ_FUNCS')
///m4_popdef(`CSEQ_FUNC_ATTR')
///)')

#define CSEQ_INIT(name, cval_t) \
CS_CASCAT(CSEQ_INIT(`name``##''',`cval_t'))

No comments: