Creé un programa ac que usa muchas funciones anidadas de la extensión gnu y ahora quiero que se ajusten estándar a ansi c.
¿Cuál es la mejor manera de transformar funciones anidadas, que acceden a algunas variables externas en algo diferente?
#define lambda(return_type, function_body) \ ({ \ return_type __fn__ function_body \ __fn__; \ })ejemplo de uso
size_t var1; size_t var2; lambda(void, (...) { // some code lambda(void, (...) { // ... // do something with var1/var2 // .. } // ... // do something with var1/var2 }Pensé en mover los vars al alcance global, por lo que se conocen de cada "lambda", lo que quizás sea la solución más fácil, pero no quiero contaminar el alcance global y no estoy seguro si esta es la forma más limpia.
Como lo preguntaron algunos comentaristas, aquí hay un ejemplo concreto
/* 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);Es parte de un compilador que crea algunos itables para búsquedas de interfaz eficientes.
En mi opinión, la forma más limpia y idiomática sería hacer funciones simples no anidadas que tomen todas las variables que necesitan como argumentos, ya sea por valor si son entradas o mediante un puntero si son salidas.
Si esto se vuelve difícil debido a la cantidad de variables, probablemente sea una señal de un problema mayor en el código y probablemente necesite una refactorización más sustancial.
Considere colocar grupos de variables estrechamente relacionadas en estructuras si desea reducir la cantidad de variables sueltas aleatorias en el código; es probable que esto también sea más expresivo. El uso de funciones en línea es generalmente un método de codificación muy desordenado, ya que promueve alcances grandes y poco claros.
Sin embargo, las macros probablemente serían una solución aún peor, ya que evitarán cualquier posibilidad que el compilador tenga de evitar repetir el código de las funciones anidadas y contaminarán el alcance externo aún más con cualquier variable definida en la función anidada.
Los globales son posiblemente la peor solución de todas, ya que amplían los alcances a todo el programa y, además, son sustancialmente más lentos de acceder y ralentizarán enormemente su código. También harán que una colisión de nombres de variables sea casi inevitable en un programa más grande.
Utiliza las devoluciones de llamada para la iteración a través de contenedores. Si su estructura de datos lo permite, podría intentar escribir código transversal por medio de iteradores, lo que le permitirá escribir lo que ahora es una devolución de llamada separada como cuerpo de bucle.
Por ejemplo, si tiene un árbol binario, el recorrido recursivo con una devolución de llamada se parece más o menos a esto:
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); } }Y se usa así:
traverse(head, lambda(void, (const node_t *n){ puts(n->id); }));Como ha señalado, en el estándar C, la función debe ser una función global con la restricción de que no puede acceder de forma fácil y segura a los datos que no están almacenados en el propio nodo.
Para una forma más intuitiva y compatible con los estándares de atravesar el árbol, puede reescribir el recorrido como código iterativo y almacenar el estado en un iterador:
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; }El código del iterador es más detallado que el recorrido recursivo, pero el código del cliente es un bucle simple, que puede acceder a otras variables locales en la función:
node_iter_t it = {head}; int i = 0; while (next_node(&it)) { printf("%d: %s\n", i++, it.node->id); }Por supuesto, es posible que su contenedor no sea adecuado para tal reescritura.