The pre-processor is a tool that runs over your code and does textual
substitution of macros before compiling. It can be handy for
generalising something you need to do often or making your code more
readable. It's often referred to as the C pre-processor, but it's
language-agnostic and can be applied to any code. Its functionality
hasn't changed much over the years so people have come up with
workarounds to fill gaps in its basic abilities. Where I've taken
techniques below from particular sources I've tried to note this.
Counting arguments:
Languages like C let you create functions which accept a variable
number of arguments. This is all well and good except that your
function needs some way of determining how many arguments it has been
passed. You can force callers to pass you the argument count or you
can use the following:
SEQ_N 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
ARG_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
NARGS(...) NARG_(, ## __VA_ARGS__, SEQ_N)
NARG_(...) ARG_N(__VA_ARGS__)
Now you can wrap your function, foo, in a macro like:
FOO(args...) foo(NARGS(args) , ## args)
This one came from
https://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5?pli=1.
It's pretty odd and you may need to trace through an example to
actually understand what's going on.
Creating unique symbols:
Sometimes you have a macro that is expanded in several places and
generates tokens that end up as symbols in your target language. This
can be disastrous when two copies of the same symbol are generated.
The pre-processor has the pre-defined symbol __LINE__ which expands to
the current line number in the source file and can be used to produce
pseudo-unique tokens, but actually using it in a symbol requires a
seemingly useless level of indirection:
_JOIN(x, y) x ## y
JOIN(x, y) _JOIN(x, y)
Now you can generate unique symbols with JOIN(my_symbol_prefix,
__LINE__). This one is from
https://www.securecoding.cert.org/confluence/display/seccode/PRE05-C.+Understand+macro+replacement+when+concatenating+tokens+or+performing+stringification.
Compile-time assertions:
Assertions are a great way of validating that your code is doing what
you think it is doing. It's even better when you can have these
checked at compile-time rather than runtime. For static conditions,
you can use the following:
compile_time_assert(name, condition) typedef char JOIN(name##_,
__LINE__)[(condition) ? 1 : -1];
If the condition is false, your compiler will generate an error that
the size of the array is invalid. This technique for compile-time
asserts has the added benefit that it has no effect on your resulting
binary. Note that I've used the JOIN macro from above so that this
macro can be used inside other macros.
Alignment:
When doing low-level programming, you sometimes care about the
alignment of pointers and structures. These macros are useful for
checking and adjusting alignment for powers of 2.
ALIGNED(x, n) (((unsigned int)(x) & ((unsigned int)(n) - 1)) == 0)
ROUND_UP(x, n) ((((unsigned int)(x) - 1) / (unsigned int)(n) + 1) *
(unsigned int)(n))
ROUND_DOWN(x, n) ((unsigned int)(x) / (unsigned int)(n) * (unsigned int)(n))
Dealing with bitfields:
In C you occasionally want to pack variables of a single bit into a
larger type. This works well, but is very error prone when doing it
manually. These macros help prevent mistakes:
BIT(n) (1<<n)
MASK(n) (BIT(n) - 1)
IS(field, attribute) ((unsigned int)(field) & BIT(attribute))
SET(field, attribute) ((field) = (typeof(field))((unsigned int)(field)
| BIT(attribute)))
UNSET(field, attribute) ((field) = (typeof(field))((unsigned
int)(field) & ~BIT(attribute)))
The first one is used to get a field with nothing but the nth bit set.
The second is used to select a particular range of bits. E.g. my_field
& MASK(10) to get the lowest 10 bits of my_field or my_field &
~MASK(10) to blank out the lowest 10 bits. The last three are used
for, respectively, testing if a bit in the field has been set, setting
a bit, unsetting a bit.
That's it for now. If you're sick of hearing about C please email me
with something you'd like to write about for next month :)