使用pre-C99编译器构建C迭代器宏

时间:2014-03-07 21:54:44

标签: c c-preprocessor c89

我的代码必须使用前C99编译器编译(我们正在进行更新,但这是一项艰巨的任务),正在考虑使用C99设计的实用程序库。特别是,这些实用程序定义了一个hashmap类型,并提供了一个宏来迭代它,类似于以下内容:

#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())

实际代码还有一些(确保迭代器名称是唯一的功能等),但这涵盖了我的问题,即1999年之前的C版本不支持初始化变量在for循环中。现在明显的解决方法是将初始化移到循环之外,代码如下:

// 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())

问题是__attribute__((cleanup(_map_iter_cleanup)))位。根据{{​​3}},它定义了附加到变量的清理代码,当变量超出范围时,它会运行。因为我们已经在for循环之外移动了迭代器声明,所以它的范围已经改变,清理代码在别处运行。不幸的是,库的其他部分依赖迭代器立即清理 - 映射会跟踪已启动的迭代器数量,并在它们全部消失之前抛出错误。

我一直在尝试并且没有想到现在这几天干净利落的方式,但我很短暂。我真的不想在C99之前的代码中重新实现宏加清理,但是这个库遍布整个地方并且更改API以包含迭代后清理调用会很痛苦,而不是提到不优雅。

有没有人以前遇到过这种事情并且知道解决方法?

编辑:我们使用GCC 4.2.2和-std = c89选项

4 个答案:

答案 0 :(得分:3)

我不完全清楚C99之前标准缺少的功能,但我认为你可以像下面这样做:

#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())

然后无论你在哪里使用它,你都必须用一个额外的大括号}附加循环。

答案 1 :(得分:2)

一种可能性是创建一个必须在C89代码中使用的替代宏。您必须扩展范围并封装操作,例如通过创建一组新的宏,如下所示:

/* 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)

对于简单的陈述,你可以这样做:

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

...对于更复杂的陈述,你可以调用一个函数。

理想情况下,当然,您只需切换到C99(至少是相关代码)并完成它。

答案 2 :(得分:1)

你可能能够逃脱:

#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())

当循环条件为false时显式调用清理函数。

不幸的是,如果有任何MAP_FOREACH循环通过break;退出,则会失败 - 迭代器将不会被清除。您可以通过添加另一个宏来解决这些问题:

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

并用此宏替换所有有问题的中断。

答案 3 :(得分:0)

gcc有声明表达式,包含局部变量,差不多forever,包括typeof扩展名:

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

(上面的链接是gcc-2.95.2手册!)。所以你应该已经准备好所有的东西来构建更清洁的东西了。