Question

My code, which must be compiled with a pre-C99 compiler (we're working on updating but it's an enormous task), is calling into a utility library designed with C99 in mind. In particular, these utilities define a hashmap type and provide a macro to iterate through it similar to the following:

#define MAP_FOREACH(key, val, map) \
    for (struct _map_iterator iter __attribute__((cleanup(_map_iter_cleanup))); \
        (key) = iter->pair->key, \
        (value) = iter->pair->value; \
        iter = iter->get_next_cb())

The actual code has a bit more to it (functionality to make sure the iterator's name is unique, etc.), but this covers the meat of my problem which is that pre-1999 versions of C don't support initializing variables inside a for loop. Now the obvious fix here would be to move the initialization outside of the loop, with code that looks like:

// Doesn't work
#define MAP_FOREACH(key, val, map) \
    struct _map_iterator iter __attribute__((cleanup(_map_iter_cleanup)));
    for (; \
        (key) = iter->pair->key, \
        (value) = iter->pair->value; \
        iter = iter->get_next_cb())

The issue is that __attribute__((cleanup(_map_iter_cleanup))) bit. According to the GCC documentation, that defines cleanup code attached to the variable, which gets run when the variable goes out of scope. Because we've moved the iterator declaration outside the for loop, its scope has changed, and the cleanup code gets run elsewhere. Unfortunately, other parts of the library rely on the iterator getting cleaned up immediately - the map keeps track of how many iterators have been initiated, and throws an error if it's destroyed before they're all gone.

I've been trying and failing to think of a clean way around this for a few days now, but I'm coming up short. I don't really want to re-implement the macro-plus-cleanup in the pre-C99 code, but this library is used all over the place and changing the API to include a post-iteration cleanup call would be painful, not to mention inelegant.

Has anyone encountered this sort of thing before and know a way around it?

EDIT: we're using GCC 4.2.2 with the -std=c89 option

Was it helpful?

Solution 2

One possibility is to create an alternative macro that must be used in C89 code. You'd have to extend the scope and encapsulate the action, such as by making a new set of macros as follows:

/* Force a compiler error if non-C99 code uses the C99 macro. */
#if __STDC_VERSION__ >= 199901L
/* C99 code */
#define MAP_FOREACH(key, val, map) \
    for (struct _map_iterator iter __attribute__((cleanup(_map_iter_cleanup))); \
        (key) = iter->pair->key, \
        (value) = iter->pair->value; \
        iter = iter->get_next_cb())
#endif

/* C89-compatible macro */
#define MAP_FOREACH_DO(key, val, map, statement) \
do{ struct _map_iterator iter __attribute__((cleanup(_map_iter_cleanup))); \
for (; \
    (key) = iter->pair->key, \
    (value) = iter->pair->value; \
    iter = iter->get_next_cb()) \
    { \
        statement; \
    } \
}while(0)

That way for simple statements you can just do something like:

MAP_FOREACH_DO(..., printf(%s=%s\n", key, value));

... and for more complex statements you can call a function.

Ideally, of course, you just switch to C99 (at least for the relevant code) and be done with it.

OTHER TIPS

I am not fully aware of the missing functionalities of pre-C99 standard, but I think you could do it like the following:

#define MAP_FOREACH(key, val, map) \
    {
        struct _map_iterator iter __attribute__((cleanup(_map_iter_cleanup)));
        for (; \
            (key) = iter->pair->key, \
            (value) = iter->pair->value; \
            iter = iter->get_next_cb())

And then wherever you use it, you'd have to append the loop with an extra closing curly bracket }.

You might be able to get away with something like:

#define MAP_FOREACH(key, val, map) \
struct _map_iterator iter;
for (; \
    (key) = iter->pair->key, \
    (value) = iter->pair->value ? 1 : _map_iter_cleanup(&iter), 0; \
    iter = iter->get_next_cb())

explicitly calling the cleanup function when the loop condition is false.

Unfortunately, this will fail if there are any MAP_FOREACH loops that exit via break; -- the iterator won't be cleaned up. You could fix these by adding another macro:

#define MAP_FOREACH_BREAK  { _map_iter_cleanup(&iter); break; }

and replacing all the problematic breaks with this macro.

gcc has had statement expressions, complete with local variables, almost forever, including the typeof extension:

({ int y = foo (); int z;
   if (y > 0) z = y;
   else z = - y;
   z; })

(the links above are to gcc-2.95.2 manuals!). So you should already have all pieces to build something cleaner at hand.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top