C Preprocessor Abuse
A lot of people recommend avoiding macros. With “
const
” and “
inline
”, a lot of the things that used to require macros can now be done in plain C code. However, the C preprocessor has a lot more to offer.
If you haven’t read
the GCC preprocessor manual, you should. It’s well written and contains a lot of things you really should know. The stuff here assumes you have.
Binary Literals
C doesn’t support binary literals, but you can use macros to fake it:
#define BIN_0000 0
#define BIN_0001 1
#define BIN_0010 2
#define BIN_0011 3
#define BIN_0100 4
#define BIN_0101 5
#define BIN_0110 6
#define BIN_0111 7
#define BIN_1000 8
#define BIN_1001 9
#define BIN_1010 a
#define BIN_1011 b
#define BIN_1100 c
#define BIN_1101 d
#define BIN_1110 e
#define BIN_1111 f
#define BIN_8_HEXIFY(b1,b2) (0x ## b1 ## b2)
#define BIN_8_RELAY(b1,b2) BIN_8_HEXIFY(b1, b2)
#define BIN_8(b1,b2) BIN_8_RELAY(BIN_ ## b1, BIN_ ## b2)
#define BIN_16_HEXIFY(b1,b2,b3,b4) (0x ## b1 ## b2 ## b3 ## b4)
#define BIN_16_RELAY(b1,b2,b3,b4) BIN_16_HEXIFY(b1, b2, b3, b4)
#define BIN_16(b1,b2,b3,b4) BIN_16_RELAY(BIN_##b1, BIN_##b2, BIN_##b3, BIN_##b4)
The reason we need those helper functions is that the preprocessor’s parameter evaluation order is strange (the GCC preprocessor manual explains it clearly).
To use it:
BIN_16(0010,1100,1010,1111)
// Gets translated to: 0x2caf
One problem is that you will get weird error messages when you use invalid digits. Since the digits are simply pasted together to form the hex literal, the error message may not be that friendly. An alternative approach would be to use the bit shift operators to construct the constant (the compiler will probably evaluate it at compile time, so there’s no performance hit; GCC 3 does this).
Another thing to watch out for is word size. If you use bit-shifting to construct the constant, make sure the operations are performed on the correct integer type (or your bits will get shifted off the end).
Here’s a ready-to-use “.h” file with macros that go up to 64 bits:
BinaryLiteral.h.
Macro-based "foreach"
That’s right. Higher-order functional programming with macros.
Let’s say you’re writing some sort of bytecode interpreter. You could use a “foreach macro” to allow genericity when dealing with opcodes:
#define FOREACH_OpCode(GEN) \
GEN(Add) GEN(Sub) \
GEN(Compare) GEN(Jump)
The “
GEN
” parameter is intented to be another macro. To create an “
enum
” for your opcodes, you could write:
#define GEN_EnumValue(NAME) OpCode_ ## NAME,
enum OpCode {
FOREACH_OpCode(GEN_EnumValue)
};
// Expands to:
enum OpCode {
OpCode_Add,
OpCode_Sub,
OpCode_Compare,
OpCode_Jump,
};
For debugging, it might be handy to have an array of opcode names. Using the special “
#
” preprocessor operator, we can create an array of opcode names:
#define GEN_StringEntry(NAME) #NAME,
const char* OpCode_NAMES[] = {
FOREACH_OpCode(GEN_StringEntry)
};
// Expands to:
const char* OpCode_NAMES[] = {
"Add",
"Sub",
"Compare",
"Jump",
};
Now you don’t have to worry about updating “
OpCode_NAMES
” when you add, remove, or reorder your opcodes.
You could also use the “foreach” to generate the jump table in an intepreter loop:
Registers regs;
while (HaveMoreOps(®s)) {
Op* op = GetNextOp(®s);
switch (op->opcode) {
#define GEN_SwitchCases(NAME) \
case OpCode_ ## NAME: Do_ ## NAME(®s); break;
FOREACH_OpCode(GEN_SwitchCases)
}
}
// Expands to:
Registers regs;
while (HaveMoreOps(®s)) {
Op* op = GetNextOp(®s);
switch (op->opcode) {
case OpCode_Add: Do_Add(®s); break;
case OpCode_Sub: Do_Sub(®s); break;
case OpCode_Compare: Do_Compare(®s); break;
case OpCode_Jump: Do_Jump(®s); break;
}
}
Variable-Length Parameter List