C 语言 宏的妙用
2016-07-07 18:55
821 查看
Editor's note: Andrew Lucas describes how to use X macros to take advantage of the
C-language pre-processor to eliminate several classes of common bugs. He also describes how to use X macros to improve developer productivity through automatic code generation.
X macros are a powerful coding technique that makes extensive use of the C-language
pre-processor. This technique has the capability to eliminate several classes of common bugs.
It seems to me that the C preprocessor gets a bad rap. Granted, there are ways to use the preprocessor inappropriately, but
to limit its use because of that constrains a valuable tool that can reduce coding errors and improve developer productivity though automatic code generation.
Code Ordering Dependencies
I discovered X macros a few years ago when I started making use of function pointers in my code. Frequently I would write
code like this:
/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, ... , STATE_N, NUM_STATES};
/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func_0, func_1, func_2, ... , func_N};
The issue with this type of code is maintainability. The ordering of the array initializers has to match the ordering of the
state code enumeration exactly. Historically I would comment this type of code liberally to warn future users about this dependency, but protection based on commenting is really no protection at all. What I needed was a tool that would automatically enforce
the dependency.
I began investigating solutions for this problem and discovered that in the C99 standard there was a new way to initialize
arrays. An improved way to write the above code is as follows:
/* declare an enumeration of state codes */
enum{STATE_0, STATE_1, STATE_2, ... , STATE_N, NUM_STATES}
/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
[STATE_1] = func_1,
[STATE_0] = func_0,
[STATE_2] = func_2,
... ,
[STATE_N] = func_N
};
Now even if I change the ordering of the enumeration, the jumptable logic doesn’t break. Much better. My only problem was
that the C compiler I was working with was not compliant with the C99 standard. Back to square one.
X macros to the Rescue
One day while talking shop with a friend of mine, I explained my problem and he suggested using the C preprocessor to enforce
the ordering. He explained the basic concept: Use preprocessor directives to define a table in the form of a macro and then redefine how the macro is expanded, as required.
Here's how this technique enforces my code ordering dependency:
#define STATE_TABLE \
ENTRY(STATE_0, func_0) \
ENTRY(STATE_1, func_1) \
ENTRY(STATE_2, func_2) \
... \
ENTRY(STATE_X, func_X)
/* declare an enumeration of state codes */
enum{
#define ENTRY(a,b) a,
STATE_TABLE
#undef ENTRY
NUM_STATES
};
/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
#define ENTRY(a,b) b,
STATE_TABLE
#undef ENTRY
};
In the case of the enumeration the table expands to ‘a’ which is the first column of the state table; the state code. In the
case of the array, the table expands to ‘b’ which is the second column, the name of the function pointer.
The code based on the X macro table is expanded in the same order for both the enumeration and the array. The preprocessor
now enforces the dependency!
Cleaning up the code
One thing I don’
d984
t like about this implementation is the presence of #define and #undefthroughout
the code, which to me is ugly and makes the code less readable. Let’s look at a technique for getting rid of them.
You will notice that in my definition of the STATE_TABLE macro
I don’t take any parameters. There is nothing to prevent me from passing the definition of ENTRY directly
to the STATE_TABLE macro
instead of defining it separately:
#define EXPAND_AS_ENUMERATION(a,b) a,
#define EXPAND_AS_JUMPTABLE(a,b) b,
#define STATE_TABLE(ENTRY) \
ENTRY(STATE_0, func_0) \
ENTRY(STATE_1, func_1) \
ENTRY(STATE_2, func_2) \
... \
ENTRY(STATE_X, func_X)
/* declare an enumeration of state codes */
enum{
STATE_TABLE(EXPAND_AS_ENUMERATION)
NUM_STATES
}
/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {
STATE_TABLE(EXPAND_AS_JUMPTABLE)
}:
Much better, but is there anything else that we could use the X macro table for? Since every function pointer corresponds
to an actual function, we could use the table to generate function prototypes for us:
#define EXPAND_AS_PROTOTYPES(a,b) static void b(void);
STATE_TABLE(EXPAND_AS_PROTOTYPES);
Now I no longer need to remember to add a prototype when I add new states. The preprocessor can take care of it and will expand
the table into the following code automatically:
static void func_0(void);
static void func_1(void);
static void func_2(void);
...
static void func_X(void);
Register Initialization
That's not the only way X macros can be used. In my code I commonly have to interface to custom FPGAs. These devices usually
have many memory mapped registers that need initialization. It's easy to forget to initialize a newly defined register, but using X macros, this is another task we can automate.
#define EXPAND_AS_INITIALIZER(a,b) a = b;
#define REGISTER_TABLE(ENTRY) \
ENTRY(reg_0, 0x11) \
ENTRY(reg_1, 0x55) \
ENTRY(reg_2, 0x1b) \
... \
ENTRY(reg_X, 0x33)
static void init_registers(void){
REGISTER_TABLE(EXPAND_AS_INITIALIZER)
}
Simple; and as new registers are added, no code needs to be updated to initialize it - we just add a row to the table and
the preprocessor does the rest. We can further improve this code to take into account not only the initialization, but the declaration of the registers:
#define FPGA_ADDRESS_OFFSET (0x8000)
#define EXPAND_AS_INITIALIZER(a,b,c) a = c;
#define EXPAND_AS_DECLARATION(a,b,c) volatile uint8_t a _at_ b;
#define REGISTER_TABLE(ENTRY) \
ENTRY(reg_0, FPGA_ADDRESS_OFFSET + 0, 0x11) \
ENTRY(reg_1, FPGA_ADDRESS_OFFSET + 1, 0x55) \
ENTRY(reg_2, FPGA_ADDRESS_OFFSET + 2, 0x1b) \
... \
ENTRY(reg_X, FPGA_ADDRESS_OFFSET + X, 0x33)
/* declare the registers */
REGISTER_TABLE(EXPAND_AS_DECLARATION)
This code uses a compiler specific directive
_at_ to place the variables at absolute addresses. This may not be possible with other compilers. Secondly, more than
one table may be required to take into account different types of register declarations. You may need to have a read-only register table, a write-only register table, an uninitialized register table, etc.
I hope that this introduction to X macros has provided a glimpse into the power of this coding technique. In Part
2 I dig a little deeper and show some more advanced uses of X macros to facilitate automatic code generation.
Part
2
Andrew Lucas leads a team of firmware developers at NCR Canada. He is responsible
for the firmware architecture of their intelligent deposit modules found inside NCR's line of ATMs.
相关文章推荐
- Linux一键部署Web环境
- 【数字图像处理学习笔记之六】基本灰度级形态学算法
- 让apache 支持cgi
- linux进程间通信-信号通信
- VR虚拟现实&AR增强现实编程(2):开发环境Unity3D简介
- 多示例学习
- APP开发流程实例讲解-儒释道网络电台八天开发全程-优化排错:增强稳定性和添加异常处理
- nginx安装
- 博客转移了
- Linux入门:常用命令:scp上传下载文件
- Android 全局异常捕获之CrashHandler
- CentOS安装NTFS-3g
- 如何使用Unix/Linux grep命令——磨刀不误砍柴工系列
- 分页,删除操作后在该页码上显示
- Android之定向广播
- 对laravel5概念的理解 -- 观察者模式(Event)
- FFmpeg 常用命令
- LA-4060(枚举)
- Spark性能调优
- js点击a标签切换不同列表