Las funciones anónimas devuelven valores asignados dinámicamente

Editar

Resumen: Básicamente, un corredor que recibe un puntero a un método de fábrica polimórfico que almacena en caché y luego llama para instanciar y ejecutar cuando ocurre un evento.

Para hacer las cosas más fáciles de transmitir, he creado un pequeño proyecto aislado con archivos de origen y de prueba. Descargar

Ejecutar make para comstackr

Ejecutar make test para ejecutar prueba

Espero que esto ayude.


La pregunta se basa en una solución de patrón de diseño fácilmente realizable en otros idiomas pero difícil de implementar en C. El código restringido se encuentra a continuación.

Sobre la base de la respuesta vinculada , estoy tratando de encontrar una solución para los valores generados dinámicamente en una función anónima.

Extracto de la respuesta:

 int (*max)(int, int) = ({ int __fn__ (int x, int y) { return x > y ? x : y; } __fn__; }); 

Código de la biblioteca estática

 struct Super{ } void add(struct Super *(*superRef)()) { // cache the reference (in some linked list) // later at some point when an event occurs. struct Super *super = superRef(); // instantiate and use it. } 

Código de cliente vinculado: Usuario del Código de la Biblioteca

 struct Sub{ struct Super *super; } add(({ struct Sub __fn__() { return malloc(sizeof(struct Sub)); } // error __fn__; })); 

Error:

 error: passing 'void' to parameter of incompatible type 'struct Sub *(*)() 

De acuerdo con la solicitud de aclaración, piense en la función de recepción en un archivo de biblioteca estática que recibe referencias a los objetos de estructura (sin instanciar). La biblioteca recibe este objeto del código del cliente.

En segundo lugar, el cliente o la biblioteca de la biblioteca estática no crea una instancia de la referencia de la estructura recibida de inmediato. Más tarde, cuando hay una notificación en el sistema, se llama a la referencia de la estructura para crear una instancia y ejecutar el rest de las cosas.

Repito, el requisito específico es mantener referencias no instanciadas a las estructuras que pasan los usuarios de la biblioteca (código de cliente). Espero que esto se aclare.

El orden correcto es:

  1. aprender c
  2. hacer magia

Simplemente no funcionará de la otra manera. ({}) no dobla la semántica por ti. Si su add espera una función que devuelve struct Super* , no funcionará con struct Sub , ni siquiera si coloca el * desaparecido allí.

Esto solo funciona en TutorialsPoint :

 #include  #include  int max(int a,int b){ if(a>b) return a; return b; } struct Super{}; void add(struct Super *(*superRef)()) { struct Super *(*secretStorage)()=superRef; /* ... */ struct Super *super = secretStorage(); /* ... */ free(super); printf("Stillalive\n"); } int main() { printf("Hello, World!\n"); int (*myMax)(int,int); // <-- that is a function pointer myMax=max; // <-- set with oldschool function printf("%d\n",myMax(1,2)); myMax = ({ // <-- set with fancy magic int __fn__ (int x, int y) { return x < y ? x : y; } __fn__; }); printf("%d - intentionally wrong\n",myMax(1,2)); add( ({ struct Super* fn(){ printf("Iamhere\n"); return malloc(sizeof(struct Super)); } fn;})); printf("Byfornow\n"); return 0; } 

Creó un pequeño proyecto de biblioteca con magia anónima incrustada en la magia anónima y la asignación de stack. No tiene mucho sentido, pero funciona:

testlib.h

 #ifndef TESTLIB_H_ #define TESTLIB_H_ struct Testruct{ const char *message; void (*printmessage)(const char *message); }; extern struct Testruct *(*nonsense())(); #endif 

testlib.c

 #include "testlib.h" #include  #include  const char *HELLO="Hello World\n"; struct Testruct *(*nonsense())(){ return ({ struct Testruct *magic(){ struct Testruct *retval=malloc(sizeof(struct Testruct)); retval->message=HELLO; retval->printmessage=({ void magic(const char *message){ printf(message); } magic; }); return retval; } magic; }); } 

prueba.c

 #include "testlib.h" #include  #include  int main(){ struct Testruct *(*factory)()=nonsense(); printf("Alive\n"); struct Testruct *stuff=factory(); printf("Alive\n"); stuff->printmessage(stuff->message); printf("Alive\n"); free(stuff); printf("Alive\n"); return 0; } 

Seguí los pasos en https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html para gcc -c -Wall -Werror -fpic testlib.c (prácticamente 3 llamadas de gcc -c -Wall -Werror -fpic testlib.c : gcc -c -Wall -Werror -fpic testlib.c , gcc -shared -o libtestlib.so testlib.o , gcc -L. -Wall -o test test.c -ltestlib y un poco de lucha con LD_LIBRARY_PATH )

El código que se muestra en la pregunta no es C estándar, sino la variante C de GNU que admite GCC. Desafortunadamente, no parece haber una etiqueta gnu-c , para especificar correctamente la variante de C involucrada.

Además, el caso de uso parece basarse en un tipo específico de paradigma orientado a objetos en una interfaz de biblioteca C. Esto es horrible, porque involucra suposiciones y características que C simplemente no tiene. Hay una razón por la que C (y GNU-C) y C ++ y Objective-C son diferentes lenguajes de progtwigción.

La respuesta simple a “funciones que devuelven valores asignados dinámicamente” donde el tipo del valor es opaco a la biblioteca, es usar void * , y para los punteros a función, (void *)() . Tenga en cuenta que en POSIX C, void * también puede contener un puntero de función.

La respuesta más compleja describiría cómo las bibliotecas como GObject admiten paradigmas orientados a objetos en C.

En la práctica, especialmente en POSIX C, usando una etiqueta de tipo (generalmente int , pero puede ser cualquier otro tipo) y una unión, se pueden implementar estructuras polimórficas, basadas en una unión de estructuras, todas con esa etiqueta de tipo como el mismo primer elemento. . El ejemplo más común de dicha funcionalidad es struct sockaddr .

Básicamente, su archivo de encabezado define una o más estructuras con el mismo miembro inicial, por ejemplo

 enum { MYOBJECT_TYPE_DOUBLE, MYOBJECT_TYPE_VOID_FUNCTION, }; struct myobject_double { int type; /* MYOBJECT_TYPE_DOUBLE */ double value; }; struct myobject_void_function { int type; /* MYOBJECT_TYPE_VOID_FUNCTION */ void (*value)(); }; 

y al final, un tipo de unión, o un tipo de estructura con una unión anónima (según lo dispuesto por C11 o GNU-C), de todos los tipos de estructura,

 struct myobject { union { struct { int type; }; /* for direct 'type' member access */ struct myobject_double as_double; struct myobject_void_function as_void_function; }; }; 

Tenga en cuenta que, técnicamente, donde sea visible esa unión, es válido convertir cualquier puntero de cualquiera de esos tipos de estructura a otro de esos tipos de estructura, y acceder al miembro de type (ver C11 6.5.2.3p6). No es necesario utilizar la unión en absoluto, es suficiente para que la unión sea definida y visible.

Aún así, para facilitar el mantenimiento (y para evitar discusiones con aspirantes a abogados de idiomas que no leyeron ese párrafo en el estándar C), recomiendo usar la estructura que contiene la unión anónima como el tipo “base” en la interfaz de la biblioteca.

Por ejemplo, la biblioteca podría proporcionar una función para devolver el tamaño real de algún objeto:

 size_t myobject_size(struct myobject *obj) { if (obj) switch (obj->type) { case MYOBJECT_TYPE_DOUBLE: return sizeof (struct myobject_double); case MYOBJECT_TYPE_VOID_FUNCTION: return sizeof (struct myobject_void_function); } errno = EINVAL; return 0; } 

Me parece que OP está intentando implementar un patrón de fábrica , donde la función de biblioteca proporciona la especificación ( clase en OOP) para el objeto creado, y un método para producir esos objetos más adelante.

La única forma en C de implementar la tipificación dinámica es a través del tipo de polymorphism que se muestra arriba. Esto significa que la especificación para los objetos futuros (nuevamente, la clase en OOP) debe ser un objeto ordinario en sí mismo.

El patrón de fábrica en sí es bastante fácil de implementar en el estándar C. El archivo de encabezado de la biblioteca contiene, por ejemplo,

 #include  /* * Generic, application-visible stuff */ struct any_factory { /* Function to create an object */ void *(*produce)(struct any_factory *); /* Function to discard this factory */ void (*retire)(struct any_factory *); /* Flexible array member; the actual size of this structure varies. */ unsigned long payload[]; }; static inline void *factory_produce(struct any_factory *factory) { if (factory && factory->produce) return factory->produce(factory); /* C has no exceptions, but does have thread-local 'errno'. The error codes do vary from system to system. */ errno = EINVAL; return NULL; } static inline void factory_retire(struct any_factory *factory) { if (factory) { if (factory->retire) { factory->retire(factory); } else { /* Optional: Poison function pointers, to easily detect use-after-free bugs. */ factory->produce = NULL; factory->retire = NULL; /* Already NULL, too. */ /* Free the factory object. */ free(factory); } } } /* * Library function. * * This one takes a pointer and size in chars, and returns * a factory object that produces dynamically allocated * copies of the data. */ struct any_factory *mem_factory(const void *, const size_t); 

donde factory_produce() es una función auxiliar que invoca a la fábrica para producir un objeto, y factory_retire() retira (descarta / libera) la propia fábrica. Aparte de la verificación de errores extra, factory_produce(factory) es equivalente a (factory)->produce(factory) , y factory_retire(factory) a (factory)->retire(factory) .

La función mem_factory(ptr, len) es un ejemplo de una función de fábrica proporcionada por una biblioteca. Crea una fábrica, que produce copias asignadas dinámicamente de los datos vistos en el momento de la llamada mem_factory() .

La implementación de la biblioteca en sí misma sería algo en la línea de

 #include  #include  #include  struct mem_factory { void *(*produce)(struct any_factory *); void (*retire)(struct any_factory *); size_t size; unsigned char data[]; }; /* The visibility of this union ensures the initial sequences in the structures are compatible; see C11 6.5.2.3p6. Essentially, this causes the casts between these structure types, for accessing their initial common members, valid. */ union factory_union { struct any_factory any; struct mem_factory mem; }; static void *mem_producer(struct any_factory *any) { if (any) { struct mem_factory *mem = (struct mem_factory *)any; /* We return a dynamically allocated copy of the data, padded with 8 to 15 zeros.. for no reason. */ const size_t size = (mem->size | 7) + 9; char *result; result = malloc(size); if (!result) { errno = ENOMEM; return NULL; } /* Clear the padding. */ memset(result + size - 16, 0, 16); /* Copy the data, if any. */ if (mem->size) memcpy(result, mem->data, size); /* Done. */ return result; } errno = EINVAL; return NULL; } static void mem_retirer(struct any_factory *any) { if (any) { struct mem_factory *mem = (struct mem_factory *)any; mem->produce = NULL; mem->retire = NULL; mem->size = 0; free(mem); } } /* The only exported function: */ struct any_factory *mem_factory(const void *src, const size_t len) { struct mem_factory *mem; if (len && !src) { errno = EINVAL; return NULL; } mem = malloc(len + sizeof (struct mem_factory)); if (!mem) { errno = ENOMEM; return NULL; } mem->produce = mem_producer; mem->retire = mem_retirer; mem->size = len; if (len > 0) memcpy(mem->data, src, len); return (struct any_factory *)mem; } 

Esencialmente, el tipo struct any_factory es en realidad polimórfico (no en la aplicación, pero solo dentro de la biblioteca). Todas sus variantes ( struct mem_factory aquí) tienen los dos punteros de función iniciales en común.

Ahora, si examinamos el código anterior y consideramos el patrón de fábrica, debería darse cuenta de que los punteros de función tienen muy poco valor: solo podría usar el tipo polimórfico que mostré anteriormente en esta respuesta, y tener las funciones integradas de productor y consumidor. Llame a funciones internas específicas de subtipo según el tipo de fábrica. factory.h :

 #ifndef FACTORY_H #define FACTORY_H #include  struct factory { /* Common member across all factory types */ const int type; /* Flexible array member to stop applications from declaring static factories. */ const unsigned long data[]; }; /* Generic producer function */ void *produce(const struct factory *); /* Generic factory discard function */ void retire(struct factory *); /* * Library functions that return factories. */ struct factory *mem_factory(const void *, const size_t); #endif /* FACTORY_H */ 

y factory.c :

 #include  #include  #include  #include "factory.h" enum { INVALID_FACTORY = 0, /* List of known factory types */ MEM_FACTORY, /* 1+(the highest known factory type) */ NUM_FACTORY_TYPES }; struct mem_factory { int type; size_t size; char data[]; }; /* The visibility of this union ensures the initial sequences in the structures are compatible; see C11 6.5.2.3p6. Essentially, this causes the casts between these structure types, for accessing their initial common members, valid. */ union all_factories { struct factory factory; struct mem_factory mem_factory; }; /* All factories thus far implemented are a single structure dynamically allocated, which makes retiring simple. */ void retire(struct factory *factory) { if (factory && factory->type > INVALID_FACTORY && factory->type < NUM_FACTORY_TYPES) { /* Poison factory type, to make it easier to detect use-after-free bugs. */ factory->type = INVALID_FACTORY; free(factory); } } char *mem_producer(struct mem_factory *mem) { /* As a courtesy for users, return the memory padded to a length multiple of 16 chars with zeroes. No real reason to do this. */ const size_t size = (mem->size | 7) + 9; char *result; result = malloc(size); if (!result) { errno = ENOMEM; return NULL; } /* Clear padding. */ memset(result + size - 16, 0, 16); /* Copy data, if any. */ if (mem->size) memcpy(result, mem->data, mem->size); return result; } /* Generic producer function. Calls the proper individual producers. */ void *factory_producer(struct factory *factory) { if (!factory) { errno = EINVAL; return NULL; } switch (factory->type) { case mem_factory: return mem_producer((struct mem_factory *)factory); default: errno = EINVAL; return NULL; } } /* Library functions that return factories. */ struct factory *mem_factory(const void *ptr, const size_t len) { struct mem_factory *mem; if (!ptr && len > 0) { errno = EINVAL; return NULL; } mem = malloc(len + sizeof (struct mem_factory)); if (!mem) { errno = ENOMEM; return NULL; } mem->type = MEM_FACTORY; mem->size = len; if (len > 0) memcpy(mem->data, ptr, len); return (struct factory *)mem; } 

Si nos fijamos en las implementaciones estándar de las bibliotecas C y POSIX C, veremos que se utilizan ambos enfoques.

La estructura estándar de FILE E / S a menudo contiene punteros de función, y las funciones fopen() , fread() , fwrite() , etc. son solo envoltorios alrededor de estos. Este es especialmente el caso si la biblioteca C admite una interfaz similar a GNU fopencookie() .

El zócalo POSIX.1, especialmente el tipo struct sockaddr , es el prototipo original de la estructura polimórfica que se muestra primero en esta respuesta. Debido a que su interfaz no admite nada similar a fopencookie() (es decir, anulando la implementación de, por ejemplo, send() , recv() , read() , write() , close() ), no hay necesidad de los punteros de función .

Por lo tanto, no pregunte cuál es el más adecuado, ya que ambos se usan con mucha frecuencia, y depende en gran medida de los detalles minuciosos. En general, prefiero el que proporciona una implementación más sencilla y proporciona toda la funcionalidad necesaria.

Personalmente, he encontrado que no es tan útil preocuparse por futuros casos de uso sin experiencia práctica y comentarios primero. En lugar de tratar de crear el mejor marco final que resuelva todos los problemas futuros, el principio KISS y la filosofía de Unix parecen dar mejores resultados.

Después de mucha lucha, aquí está la solución, pero gracias a la comunidad por la ayuda.

La primera comunidad me dijo que las funciones anónimas no son parte de C, por lo que la sugerencia alternativa es usar funciones con nombre y puntero a ellas.

En segundo lugar, un puntero a una estructura principal no puede recibir un puntero a su tipo derivado (Estructura principal integrada), por lo que no puedo hacer mucho allí. Intenté usar void * pero tal vez exista una solución utilizando la dirección de memoria y luego acceda a algún miembro de la estructura sin realizar la conversión a tipos específicos. Te lo preguntaré en otra pregunta.

Lo que me falta es la capacidad de llamar al súper método desde el método de ejecución anulado de alguna manera?

src / super.h

 struct Super { void (*run)(); }; struct Super *newSuper(); 

src / super.c

 static void run() { printf("Running super struct\n"); } struct Super *newSuper() { struct Super *super = malloc(sizeof(struct Super)); super->run = run; return super; } 

src / Runner.h

 struct Runner { void (*addFactoryMethod)(struct Super *(*ref)()); void (*execute)(); }; struct Runner *newRunner(); 

src / runner.c

 struct Super *(*superFactory)(); void addFactoryMethod(struct Super *(*ref)()) { superFactory = ref; } static void execute() { struct Super *sup = superFactory(); // calling cached factory method sup->run(); } struct Runner *newRunner() { struct Runner *runner = malloc(sizeof(struct Runner)); runner->addFactoryMethod = addFactoryMethod; runner->execute = execute; return runner; } 

prueba / runner_test.c

 void anotherRunMethod() { printf("polymorphism working\n"); // how can i've the ability to call the overridden super method in here? } struct Super *newAnotherSuper() { struct Super *super = malloc(sizeof(struct Super)); super->run = anotherRunMethod; return super; } void testSuper() { struct Runner *runner = newRunner(); runner->addFactoryMethod(&newAnotherSuper); runner->execute(); } int main() { testSuper(); return 0; } 

(Citando tu respuesta aceptada a ti mismo)

En segundo lugar, un puntero a una estructura principal no puede recibir un puntero a su tipo derivado (Estructura principal integrada), por lo que no puedo hacer mucho allí. Intenté usar void *, pero tal vez exista una solución que use la dirección de memoria y luego acceda a algún miembro de la estructura sin convertir a tipos específicos. Te lo preguntaré en otra pregunta.

Este es otro indicador de que uno debe aprender primero lo básico. Lo que extrañas se llama ‘statement de reenvío’:

 struct chicken; // here we tell the compiler that 'struct chicken' is a thing struct egg{ struct chicken *laidby; // while the compiler knows no details about 'struct chicken', // its existence is enough to have pointers for it }; struct chicken{ // and later it has to be declared properly struct egg *myeggs; }; 

Lo que me falta es la capacidad de llamar al súper método desde el método de ejecución anulado de alguna manera?

Estos no son métodos y no hay anulación. En su código no ocurre OOP, C es un lenguaje de progtwigción de procedimientos. Si bien hay extensiones de POO para C, realmente no debes ir por ellas sin saber lo básico de C.