开发者

How evil is this foreach C macro?

开发者 https://www.devze.com 2023-02-11 03:13 出处:网络
The preface to this question is that, I realize C macros are a touchy subject.Many time they can be accomplished by a non-macro solution that is more secure and not subject to classic problems like in

The preface to this question is that, I realize C macros are a touchy subject. Many time they can be accomplished by a non-macro solution that is more secure and not subject to classic problems like incremented arguments; so with that out of the way, I have a hash table implementation in C with linked nodes for collision. I'm sure most have seen this a million times but it goes a bit like this.

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
        ...
}

I would like to provide an abstracted way of iterating through the nodes. I considered using a function which takes a function pointer and applies the function to each node but I often find this kind of solution very limiting so I came up with this macro:

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

Which can be used like:

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

The only downside I can see is that the iterator index is a part of the table so obviously you could not have two loops going on at the same time in the same table. I am not sure how to resolve this but I don't see it as a deal breaker considering how useful this little macro would be. So my question.

** updated **

I incorporated Zack and Jens's suggestion, removing the problem with "else" and declaring the iterator inside the for statement. Everything appears to work but visual studio complains that "type name is not allowed" where the macro is used. I am 开发者_运维百科wondering what exactly happens here because it compiles and runs but I am not sure where the iterator is scoped.

#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

Is this approach bad form and if not is there any way to improve it?


The only really unacceptable thing in there is what you already said -- the iterator is part of the table. You should pull that out like so:

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);
}

I also sucked the if statement into the for-loop expresssions because it's slightly safer that way (weird things will not happen if the next token after the close brace of the loop body is else). Note that the array entry table->nodes[table->node_count] will be read from, unlike the usual convention, so you need to allocate space for it (and make sure it's always NULL). This was true with your version as well, I think.

EDIT: Corrected logic for case where a table entry is NULL.


In addition to Zack's answer about the iterator and about the terminating if that may obfuscate the syntax:

If you have C99, use local variables in the for loop. This will avoid bad surprises of variables of the surrounding scope that will hold dangling pointers. Use something like this inside the macro:

for(nodeS node = ...

And, names with _t at the end are reserved by POSIX. So better not use them for your own types.

0

精彩评论

暂无评论...
验证码 换一张
取 消