Defining new control structures using C macros
While procrastinating today, I stumbled upon a blog post describing how to start some background work from C code in a convenient way. The solution the author came in with allows a developer to write code such as:
int baz(void)
{
do_something();
start_deferred_work();
do_something_else();
end_deferred_work();
do_something_different();
return some_value;
}
The implementation uses two macros, start_deferred_work
to fork the current process and end_deferred_work
to terminate the child process using exit(0)
.
I do not intend to discuss the validity or the efficiency of this method. I am more interested in how the author could have introduced a new control structure instead of those two macros. The main reason I do not like them is that they have a risk of letting you with an open scope if you forget the end_deferred_work
macro, and they do not play nicely with automatic indentation, as the implicitly created scope does not visually appear.
Many languages allow you to introduce new control structure through delayed evaluation blocks (e.g., Scala, Factor, Smalltalk, Ruby) or some macro expansion (e.g., Lisp-like languages). This is not directly possible in C as the C preprocessor only allows basic manipulations. However, those text manipulations can sometimes be sufficient to mimic new control structures.
Let us rewrite those two macros as a pseudo control-structure named detach
. To acknowledge the fact that after forking we need to perform two actions (execute the user-supplied code, the exit the child process) we will use a for
loop as a way to invert two actions A()
and B()
: if B()
evaluates to false or makes the current thread terminate, then A(); B();
may be written as for (;; B()) A();
. Since exit(0);
makes the current process quit, we can use this for
construct to insert a call to exit(0);
after the user-supplied code:
#include <stdlib.h>
#include <unistd.h>
#define detach if (fork()); else for (;; exit(0))
After this definition, we can use detach
as if it were a built-in C control structure comparable to for
or while
:
/* Example 1 */
detach
do_something();
/* Example 2 */
detach {
do_something();
do_something_else();
}
/* Example 3 */
detachfor (int i = 0; i < 2; i++)
do_something(i);
/* Example 4 */
if (execute_in_background)
/* Execute on background */
detach do_something(); else
/* Execute on foreground */ do_something();
The astute reader will wonder why, in the macro definition snippet, we used a surprising if (fork()); else
instead of if (!fork())
which might first appear to be equivalent. The reason to do so is that both constructs may sometimes have different effects: in C, an else
is paired with the closest same-scope if
without a else
clause. This allows the compiler to parse code such as
if (condition)
if (other_condition) do_something(); else do_something_else();
without ambiguity. Here, we want users of our macro to be able to use it with a similar if clause as is the case in example 4. If we used the incorrect if (!fork())
code, we would end up with the following macro expansion (indented to show how the compiler understands it):
if (execute_in_background)
if (!fork())
for (;; exit(0))
do_something();else
do_something_else();
According to the rule we described earlier, the else do_something_else();
part would be matched with the if (!fork())
in line 2 instead of the if (execute_in_background)
in line 1. If execute_in_background
is true, do_something()
and do_something_else()
will both be executed (the former in the child process, the latter in the parent process), and if execute_in_background
is false, none of the functions will be called.
Using our proposed macro, the expansion will give (once again indented as the compiler understands it)
if (execute_in_background)
if (fork());
else
for (;; exit(0))
do_something();else
do_something_else();
which correctly matches the latest else
with the right if
.
For the same reason, we can properly nest calls to our macro:
#include <stdio.h>
int main(void)
{"In parent (pid = %d)\n", getpid());
printf(
detach {"In child (pid = %d, ppid = %d)\n", getpid(), getppid());
printf("In grand child (pid = %d, ppid = %d)\n", getpid(), getppid());
detach printf("Still in child here (pid = %d)\n", getpid());
printf(
}"Still in parent (pid = %d)\n", getpid());
printf( }
gives on my system
In parent (pid = 31402)
Still in parent (pid = 31402)
In child (pid = 31403, ppid = 31402)
Still in child here (pid = 31403)
In grand child (pid = 31404, ppid = 31403)
So the C macro system, although very limited, lets you define new control structures to use in your code. In C++, the Boost library makes an heavy use of them to define, for example, new iterators.
Note that, once again, I am not discussing here the appropriateness of spawning a child process to execute background code, but only a better way of implementing the original proposal.
Aren’t C macros cool?
Edit: the initial version of this article used a more complicated macro which required the compiler to respect the C99 standard. A commenter pointed out a simpler form, which is the form now used here.