Variadic null-pointer check macro
In C, raw pointer handling is one of the most common things that one will do.
Given that pointers can be NULL
, it is important to check them prior to every
possible use.
As such, in C one ends up writing something like the following quite often.
int foo(int* ptr1, void* ptr2, struct Bar* ptr3) {
if(NULL == ptr1 || NULL == ptr2 || NULL == ptr3) {
//indicate an error as we need all pointers to be != NULL
return -1;
}
//do something useful here
return 0;
}
Needless to say that this can become even more cumbersome, f.e. , when a structure has pointers as fields.
So, how can one cut down on that abhorrend pointer checking code that you need to write?
The macro
I eventually got tired of starting almost every function with a pointer check akin to the above example, and thus set out to find an alternative.
all_not_null(orelse, ...)
works almost like a function zoo(orelse, arg1, arg2, ..., argN)
and expands to
if(NULL == arg1 || NULL == arg2 || ... || argN) {
orelse
}
Used with the above example, it would simplify that to
int foo(int* ptr1, void* ptr2, struct Bar* ptr3) {
all_not_null(return -1;, ptr1, ptr2, ptr3)
//do something useful here
return 0;
}
Caveats
There are three caveats to it, though:
- This requires a C11-compliant preprocessor
- There’s only a guarantee within the standard to support at least 64 arguments to a macro.
Given that this also takes the
orelse
portion, N has to be less than or equal to 63. - The implementation is ugly, see below.
implementation
The naive (and wrong) implementation approach is to simply have a function that takes a vararg list. One won’t notice anything being wrong with it until you check a pointer to a structure and a field within that structure that is also a pointer.
By supplying ptr, ptr->field
as the arguments to such a function, one would always incur a segfault (and possibly
exploitable null-pointer dereference) if NULL == ptr
as the ptr->field
expression is evaluated prior to calling the
function.
Due to this, you have to turn all_not_null
and the subsequently called stuff into macros.
core macro
#define all_not_null(orelse, ...) \
if(ann_dispatch_1(ann_expand, PP_NARG(__VA_ARGS__))(__VA_ARGS__)) { \
orelse \
}
It simply calls the first-stage dispatcher with a macro-name prefix and the number of arguments that are hiding in the
...
portion.
The PP_NARG
macro was taken from this comp.std.c
post, and determines the number of arguments that
are captured in ...
, and __VA_ARGS__
respectively.
auxiliary macros
#define ann_dispatch_1(func, nargs) ann_dispatch_2(func, nargs)
#define ann_dispatch_2(func, nargs) func##nargs
The ann_dispatch_1
is the first half of a dispatcher chain that will eventually call the proper macro expansion inside
of ann_dispatch_2
. Due to idiosyncracies with how ##
works in the preprocessor, 2 stages are required.
#include <stddef.h>
#define ann_expand1(p1) NULL == p1
#define ann_expand2(p1, p2) ann_expand1(p1) || ann_expand1(p2)
#define ann_expand3(p1, p2, p3) ann_expand2(p1, p2) || ann_expand1(p3)
#define ann_expand4(p1, p2, p3, p4) ann_expand3(p1, p2, p3) || ann_expand1(p4)
//...
#define ann_expand62(p1, /*...*/, p62) ann_expand61(p1, /*...*/, p61) || ann_expand1(p62)
The inclusion of stddef.h
is needed for the definition of NULL
therein, and the ann_expand2
through ann_expand62
macros
were generated with a very simple python script.
Conclusion
The macro can be useful in removing redundant typing in somewhat larger projects, but as shown, it comes with its own set of caveats.
If you want to use it, feel free to download the entire implementation