这个 foreach C 宏有多邪恶?
这个问题的前言是,我意识到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
这种方法是否不好?如果不是,有什么方法可以改进吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
唯一真正不可接受的事情就是你已经说过的——迭代器是表的一部分。你应该像这样把它拉出来:
我还将
if
语句吸进了 for 循环表达式中,因为这样更安全一些(如果循环的右大括号后面的下一个标记,则不会发生奇怪的事情正文是else
)。请注意,与通常的约定不同,将读取数组条目table->nodes[table->node_count]
,因此您需要为其分配空间(并确保它始终为 NULL)。我认为您的版本也是如此。编辑:更正了表条目为 NULL 的情况的逻辑。
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:
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 iselse
). Note that the array entrytable->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.
除了 Zack 关于迭代器和终止
if
的回答之外,这可能会混淆语法:如果您有 C99,请在 for 循环中使用局部变量。这将避免周围作用域的变量持有悬空指针时出现意外情况。在宏中使用类似这样的内容:
并且,末尾带有
_t
的名称由 POSIX 保留。所以最好不要将它们用于您自己的类型。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:
And, names with
_t
at the end are reserved by POSIX. So better not use them for your own types.