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++)
(i);
do_something
/* Example 4 */
if (execute_in_background)
(); /* Execute on background */
detach do_somethingelse
(); /* 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_somethingelse
(); 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_somethingelse
(); 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.