这个foreach C宏有多邪恶?

时间:2011-02-18 15:55:36

标签: c macros hashtable c-preprocessor

这个问题的前言是,我意识到 C 宏是一个敏感话题。很多时候,他们可以通过非宏观解决方案来实现,这种解决方案更安全,不受经典问题的影响,例如增加的参数;因此,在 C 中有一个哈希表实现,其中链接节点用于冲突。我敢肯定大多数人已经看过这一百万次,但有点像这样。

typedef struct tnode_t {
    char* key; void* value; struct tnode_t* next;
} tnode_t;

typedef struct table_t {
    tnode_t** nodes;
    unsigned long node_count;
    unsigned long iterator; // see macro below
        ...
}

我想提供一种迭代遍历节点的抽象方式。我考虑使用一个函数,它接受一个函数指针并将函数应用于每个节点,但我经常发现这种解决方案非常有限,所以我想出了这个宏:

#define tbleach(table, node) \
    for(node=table->nodes[table->iterator=0];\
        table->iterator<table->node_count;\
        node=node?node->next:table->nodes[++table->iterator])\
            if (node)

可以使用:

tnode_t* n;
tbleach(mytable, n) {
    do_stuff_to(n->key, n->value);
}

我能看到的唯一缺点是迭代器索引是表的一部分,所以显然你不能在同一个表中同时进行两个循环。我不知道如何解决这个问题,但我不认为这是一个交易破坏者,考虑到这个小宏将有多大用处。所以我的问题。

** 已更新 **

我收集了Zack和Jens的建议,用“else”删除了问题,并在for语句中声明了迭代器。一切似乎都有效,但是visual studio抱怨使用宏时“不允许使用类型名称”。我想知道这里到底发生了什么,因为它编译并运行但我不确定迭代器的作用域。

#define tbleach(table, node) \
    for(node=table->nodes[0], unsigned long i=0;\
        i<table->node_count;\
        node=node?node->next:table->nodes[++i])\
        if (!node) {} else

这种做法是不好的形式,如果没有,有什么方法可以改善吗?

2 个答案:

答案 0 :(得分:7)

那里唯一真正不可接受的的东西是你已经说过的 - 迭代器是表的一部分。你应该这样做:

typedef unsigned long table_iterator_t;
#define tbleach(table, iter, node) \
    for ((iter) = 0, (node) = (table)->nodes[(iter)]; \
         (iter) < (table)->node_count; \
         (node) = ((node) && (node)->next) \
                  ? (node)->next : (table)->nodes[++(iter)])

// use:
table_iterator_t i;
tnode_t *n;
tbleach(mytable, i, n) {
    do_stuff_to(n->key, n->value);
}

我还将if语句写入for循环表达式,因为它更安全(如果循环体的闭括号之后的下一个标记为else,则不会发生奇怪的事情) 。请注意,与通常的约定不同,将读取数组条目table->nodes[table->node_count] ,因此您需要为其分配空间(并确保它始终为NULL)。我认为你的版本也是如此。

编辑:修正了表条目为NULL的情况的逻辑。

答案 1 :(得分:1)

除了Zack关于迭代器的回答以及可能会混淆语法的终止if

如果您有C99,请在for循环中使用局部变量。这将避免周围范围的变量的不良意外,这些变量将保留悬空指针。在宏中使用类似的东西:

for(nodeS node = ...

并且,末尾的_t名称由POSIX保留。所以最好不要将它们用于自己的类型。