替换嵌套函数
Replace nested functions
我创建了一个 c 程序,它大量使用 gnu 扩展中的嵌套函数,现在我想让它们符合 ansi c 的标准。
转换嵌套函数的最佳方法是什么,嵌套函数访问一些外部变量到不同的东西。
#define lambda(return_type, function_body) \
({ \
return_type __fn__ function_body \
__fn__; \
})
用法示例
size_t var1;
size_t var2;
lambda(void, (...) {
// some code
lambda(void, (...) {
// ...
// do something with var1/var2
// ..
}
// ...
// do something with var1/var2
}
我考虑过将 vars 移动到全局范围,这样它们就可以从每个 "lambda" 中获知,这可能是最简单的解决方案,但我不想污染全局范围,我不确定,如果这是最干净的方式。
正如一些评论者所问 - 这是一个具体的例子
/* fill itt*/
int n_method = 0;
void *add_method = lambda(void, (ir_entity *method) {
int itable_offset = n_method++;
const char *method_name = get_entity_name(method);
ir_entity *implementation = get_method_entity(klass, method_name);
if (implementation == NULL) {
walk_up_callback(klass, lambda(bool, (ir_type *st) {
implementation = get_method_entity(st, method_name);
if (implementation != NULL) {
insert_itable_method(implementation, itable_offset, interface, init);
}
return implementation == NULL;
}), NULL);
} else {
insert_itable_method(implementation, itable_offset, interface, init);
}
});
walk_up_callback(interface, NULL, lambda(void, (ir_type *klass) {
if (oo_get_class_is_interface(klass)) {
walk_table_methods_callback(add_method, klass);
}
}));
walk_table_methods_callback(add_method, interface);
它是编译器的一部分,它为高效的接口查找创建一些 itables
在我看来,最干净、最惯用的方法是制作简单的非嵌套函数,将它们需要的所有变量作为参数,如果它们是输入则按值,如果它们是输入则通过指针输出。
如果由于变量的数量而变得困难,这可能是代码中存在更大问题的迹象,可能需要进行更多的重构。
如果您想减少代码中随机松散变量的数量,请考虑将密切相关的变量组放入结构中,这可能也更具表现力。使用内联函数通常是一种非常不整洁的编码方法,因为它会导致范围变大、不清晰。
然而,宏可能是一个更糟糕的解决方案,因为它们会阻止编译器避免重复嵌套函数代码的任何机会,并且会用嵌套函数中定义的任何变量进一步污染外部作用域。
全局变量可能是所有解决方案中最糟糕的,因为它们将范围扩大到整个程序,而且访问速度要慢得多,并且会极大地降低您的代码速度。它们还将使变量名冲突在较大的程序中几乎不可避免。
您使用回调通过容器进行迭代。如果你的数据结构允许,你可以尝试通过迭代器的方式编写遍历代码,这将允许你将现在是一个单独的回调编写为循环体。
例如,如果您有一个二叉树,带有回调的递归遍历大致如下所示:
typedef struct node_t node_t;
struct node_t {
const char *id;
node_t *left, *right;
};
void traverse(const node_t *node, void (*func)(const node_t *n))
{
if (node) {
traverse(node->left, func);
func(node);
traverse(node->right, func);
}
}
它是这样使用的:
traverse(head, lambda(void, (const node_t *n){ puts(n->id); }));
正如您所注意到的,在标准 C 中,该函数必须是一个全局函数,但有一个限制,即您无法轻松且类型安全地访问未存储在节点本身中的数据。
为了以符合标准且更直观的方式遍历树,您可以将遍历重写为迭代代码并将状态存储在迭代器中:
typedef struct node_iter_t node_iter_t;
struct node_iter_t {
node_t *next;
node_t *node;
node_t *stack[32];
int nstack;
};
int next_node(node_iter_t *it)
{
it->node = it->next;
while (it->nstack || it->node) {
while (it->node) {
it->stack[it->nstack++] = it->node;
it->node = it->node->left;
}
it->node = it->stack[--it->nstack];
it->next = it->node->right;
return 1;
}
return 0;
}
迭代器代码比递归遍历更冗长,但客户端代码是一个简单的循环,可以访问函数中的其他局部变量:
node_iter_t it = {head};
int i = 0;
while (next_node(&it)) {
printf("%d: %s\n", i++, it.node->id);
}
当然,您的容器可能不适合这样的重写。
我创建了一个 c 程序,它大量使用 gnu 扩展中的嵌套函数,现在我想让它们符合 ansi c 的标准。
转换嵌套函数的最佳方法是什么,嵌套函数访问一些外部变量到不同的东西。
#define lambda(return_type, function_body) \
({ \
return_type __fn__ function_body \
__fn__; \
})
用法示例
size_t var1;
size_t var2;
lambda(void, (...) {
// some code
lambda(void, (...) {
// ...
// do something with var1/var2
// ..
}
// ...
// do something with var1/var2
}
我考虑过将 vars 移动到全局范围,这样它们就可以从每个 "lambda" 中获知,这可能是最简单的解决方案,但我不想污染全局范围,我不确定,如果这是最干净的方式。
正如一些评论者所问 - 这是一个具体的例子
/* fill itt*/
int n_method = 0;
void *add_method = lambda(void, (ir_entity *method) {
int itable_offset = n_method++;
const char *method_name = get_entity_name(method);
ir_entity *implementation = get_method_entity(klass, method_name);
if (implementation == NULL) {
walk_up_callback(klass, lambda(bool, (ir_type *st) {
implementation = get_method_entity(st, method_name);
if (implementation != NULL) {
insert_itable_method(implementation, itable_offset, interface, init);
}
return implementation == NULL;
}), NULL);
} else {
insert_itable_method(implementation, itable_offset, interface, init);
}
});
walk_up_callback(interface, NULL, lambda(void, (ir_type *klass) {
if (oo_get_class_is_interface(klass)) {
walk_table_methods_callback(add_method, klass);
}
}));
walk_table_methods_callback(add_method, interface);
它是编译器的一部分,它为高效的接口查找创建一些 itables
在我看来,最干净、最惯用的方法是制作简单的非嵌套函数,将它们需要的所有变量作为参数,如果它们是输入则按值,如果它们是输入则通过指针输出。
如果由于变量的数量而变得困难,这可能是代码中存在更大问题的迹象,可能需要进行更多的重构。
如果您想减少代码中随机松散变量的数量,请考虑将密切相关的变量组放入结构中,这可能也更具表现力。使用内联函数通常是一种非常不整洁的编码方法,因为它会导致范围变大、不清晰。
然而,宏可能是一个更糟糕的解决方案,因为它们会阻止编译器避免重复嵌套函数代码的任何机会,并且会用嵌套函数中定义的任何变量进一步污染外部作用域。
全局变量可能是所有解决方案中最糟糕的,因为它们将范围扩大到整个程序,而且访问速度要慢得多,并且会极大地降低您的代码速度。它们还将使变量名冲突在较大的程序中几乎不可避免。
您使用回调通过容器进行迭代。如果你的数据结构允许,你可以尝试通过迭代器的方式编写遍历代码,这将允许你将现在是一个单独的回调编写为循环体。
例如,如果您有一个二叉树,带有回调的递归遍历大致如下所示:
typedef struct node_t node_t;
struct node_t {
const char *id;
node_t *left, *right;
};
void traverse(const node_t *node, void (*func)(const node_t *n))
{
if (node) {
traverse(node->left, func);
func(node);
traverse(node->right, func);
}
}
它是这样使用的:
traverse(head, lambda(void, (const node_t *n){ puts(n->id); }));
正如您所注意到的,在标准 C 中,该函数必须是一个全局函数,但有一个限制,即您无法轻松且类型安全地访问未存储在节点本身中的数据。
为了以符合标准且更直观的方式遍历树,您可以将遍历重写为迭代代码并将状态存储在迭代器中:
typedef struct node_iter_t node_iter_t;
struct node_iter_t {
node_t *next;
node_t *node;
node_t *stack[32];
int nstack;
};
int next_node(node_iter_t *it)
{
it->node = it->next;
while (it->nstack || it->node) {
while (it->node) {
it->stack[it->nstack++] = it->node;
it->node = it->node->left;
}
it->node = it->stack[--it->nstack];
it->next = it->node->right;
return 1;
}
return 0;
}
迭代器代码比递归遍历更冗长,但客户端代码是一个简单的循环,可以访问函数中的其他局部变量:
node_iter_t it = {head};
int i = 0;
while (next_node(&it)) {
printf("%d: %s\n", i++, it.node->id);
}
当然,您的容器可能不适合这样的重写。