Les fonctions anonymes renvoient des valeurs allouées dynamicment

modifier

Résumé: En gros, un Runner reçoit un pointeur sur une méthode de fabrique polymorphe qu’il met en cache et appelle ultérieurement pour instancier et exécuter lorsqu’un événement se produit.

Pour rendre les choses plus faciles à transmettre, j’ai créé un petit projet isolé avec des fichiers source et des fichiers de test. Télécharger

Exécuter make pour comstackr

Exécuter make test pour exécuter test

J’espère que cela t’aides.


La question est basée sur une solution de modèle de conception facilement réalisable dans d’autres langues mais difficile à implémenter en C. Le code restreint est présenté ci-dessous.

En me basant sur la réponse liée, j’essaie de trouver une solution pour les valeurs générées dynamicment dans une fonction anonyme.

Extrait de la réponse:

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

Code de bibliothèque statique

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

Code client lié: utilisateur du code de bibliothèque

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

Erreur:

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

Selon la demande de clarification, pensez à la fonction de réception dans un fichier de bibliothèque statique recevant des références aux objects de structure (non instanciées). La bibliothèque reçoit cet object du code client.

Deuxièmement, la bibliothèque du client ou de la bibliothèque statique n’instancie pas immédiatement la référence de structure reçue. Plus tard, lorsqu’il y a une notification dans le système, la référence à la structure est appelée pour instancier et exécuter le rest du travail.

Je le répète, l’exigence spécifique consiste à conserver des références non instanciées aux structures transmises par les utilisateurs de la bibliothèque (code client). J’espère que ça efface.

    Le bon ordre est:

    1. apprendre c
    2. faire de la magie

    Cela ne fonctionnera tout simplement pas dans l’autre sens. ({}) ne plie pas la sémantique pour vous. Si votre add attend une fonction qui retourne la struct Super* , cela ne fonctionnera pas avec la struct Sub , même si vous y mettez le * manquant.

    Cela fonctionne simplement sur 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; } 

    Création d'un petit projet de bibliothèque avec magie anonyme intégrée à la magie anonyme et à l'allocation de tas. Cela n’a pas beaucoup de sens, mais cela fonctionne:

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

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

    J'ai suivi les étapes décrites dans https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html pour créer un programme en cours d'exécution (pratiquement 3 appels 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 et un peu de combat avec LD_LIBRARY_PATH )

    Le code indiqué dans la question n’est pas le standard C, mais la variante GNU C prise en charge par GCC. Malheureusement, il ne semble pas y avoir de balise gnu-c pour spécifier correctement la variante de C impliquée.

    En outre, le cas d’utilisation semble reposer sur un type spécifique de paradigme orienté object dans une interface de bibliothèque C. C’est horrible, car cela implique des hypothèses et des caractéristiques que C n’a tout simplement pas. Il y a une raison pour laquelle C (et GNU-C) et C ++ et Objective-C sont des langages de programmation différents.

    La réponse simple aux “fonctions renvoyant des valeurs allouées dynamicment” où le type de la valeur est opaque pour la bibliothèque consiste à utiliser void * , et pour les pointeurs de fonction, (void *)() . Notez que dans POSIX C, void * peut également contenir un pointeur de fonction.

    La réponse plus complexe décrirait comment des bibliothèques comme GObject prennent en charge les paradigmes orientés object en C.

    En pratique, en particulier dans POSIX C, en utilisant une balise de type (généralement int , mais ce peut être tout autre type) et une union, on peut mettre en oeuvre des structures polymorphes, basées sur une union de structures avec toutes cette balise de type comme même premier élément . L’exemple le plus courant d’une telle fonctionnalité est struct sockaddr .

    Fondamentalement, votre fichier d’en-tête définit une ou plusieurs structures avec le même membre initial, par exemple

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

    et à la fin, un type d’union ou un type de structure avec une union anonyme (fournie par C11 ou GNU-C), de tous les types de structure,

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

    Notez que techniquement, chaque fois que cette union est visible, il est correct de convertir tout pointeur de l’un de ces types de structure en un autre de ces types de structure et d’accéder au membre de type (voir C11 6.5.2.3p6). Il n’est pas nécessaire d’utiliser le syndicat, il suffit que le syndicat soit défini et visible.

    Néanmoins, pour des raisons de facilité de maintenance (et pour éviter les disputes avec les juristes spécialistes des langues qui n’ont pas lu ce paragraphe dans la norme C), je recommande d’utiliser la structure contenant l’union anonyme comme type “de base” dans l’interface de la bibliothèque.

    Par exemple, la bibliothèque peut fournir une fonction permettant de renvoyer la taille réelle d’un object:

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

    Il me semble qu’OP tente d’implémenter un modèle d’usine , dans lequel la fonction de bibliothèque fournit la spécification ( classe dans la POO) de l’object créé et une méthode pour produire ces objects ultérieurement.

    En C, le seul moyen d’implémenter le typage dynamic consiste à utiliser le type de polymorphism que je viens de montrer. Cela signifie que la spécification pour les objects futurs (encore une fois, la classe dans la POO) doit être un object ordinaire lui-même.

    Le modèle d’usine lui-même est assez facile à implémenter en C standard. Le fichier d’en-tête de la bibliothèque contient par exemple

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

    factory_produce() est une fonction d’assistance qui appelle la fabrique pour produire un object, et factory_retire() supprime (supprime / libère) la fabrique elle-même. Outre la vérification d’erreur supplémentaire, factory_produce(factory) équivaut à (factory)->produce(factory) , et factory_retire(factory) à (factory)->retire(factory) .

    La fonction mem_factory(ptr, len) est un exemple de fonction factory fournie par une bibliothèque. Il crée une fabrique qui produit des copies allouées dynamicment des données vues lors de l’appel de mem_factory() .

    L’implémentation de la bibliothèque elle-même ressemblerait à

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

    Essentiellement, le type struct any_factory est en réalité polymorphe (pas dans l’application, mais dans la bibliothèque uniquement). Toutes ses variantes ( struct mem_factory ici) ont les deux pointeurs de fonction initiaux en commun.

    Maintenant, si nous examinons le code ci-dessus et considérons le modèle d’usine, vous devez vous rendre compte que les pointeurs de fonction fournissent très peu de valeur: vous pouvez simplement utiliser le type polymorphe que j’ai montré plus haut dans cette réponse et avoir les fonctions de producteur et de consommateur intégrées appelez des fonctions internes spécifiques au sous-type en fonction du type d’usine. usine.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 */ 

    et 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 nous examinons les implémentations de bibliothèques C et POSIX C standard, nous verrons que ces deux approches sont utilisées.

    La structure standard I / O FILE contient souvent des pointeurs de fonction, et les fonctions fopen() , fread() , fwrite() , etc. ne font que les envelopper. C’est particulièrement le cas si la bibliothèque C prend en charge une interface similaire à GNU fopencookie() .

    Le socket POSIX.1, en particulier le type struct sockaddr , est le prototype original de la structure polymorphe indiquée en premier dans cette réponse. Parce que leur interface ne supporte rien de similaire à fopencookie() (c’est-à-dire, écrasant l’implémentation de send() , recv() , read() , write() , close() ), les pointeurs de fonction ne sont pas nécessaires .

    Donc, ne demandez pas lequel est le plus approprié, car les deux sont très couramment utilisés, et cela dépend beaucoup des détails les plus infimes. En général, je préfère celui qui offre une implémentation plus simple avec toutes les fonctionnalités nécessaires.

    J’ai personnellement constaté qu’il n’était pas très utile de s’inquiéter des cas d’utilisation futurs sans expérience pratique et sans retour préalable. Plutôt que d’essayer de créer le cadre ultime, le meilleur jamais conçu pour résoudre tous les problèmes à venir, le principe KISS et la philosophie Unix semblent donner de bien meilleurs résultats.

    Après beaucoup de difficultés, voici la solution, mais merci à la communauté pour l’aide.

    La première communauté m’a dit que les fonctions anonymes ne font pas partie de C, alors la suggestion alternative consiste à utiliser des fonctions nommées et un pointeur sur celle-ci.

    Deuxièmement, un pointeur sur une structure parent ne peut pas recevoir de pointeur sur son type dérivé (structure parent incorporée), je ne peux donc pas en faire beaucoup plus. J’ai essayé d’utiliser void * mais une solution pourrait peut-être exister en utilisant une adresse mémoire et ensuite accéder à un membre de la structure sans transtyper en types spécifiques. Je vais poser cette question dans une autre question.

    Ce qui me manque, c’est la possibilité d’appeler la super méthode à partir de la méthode d’exécution substituée d’une manière ou d’une autre?

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

    test / 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; } 

    (Citation de votre réponse acceptée à vous-même)

    Deuxièmement, un pointeur sur une structure parent ne peut pas recevoir de pointeur sur son type dérivé (structure parent incorporée), je ne peux donc pas en faire beaucoup plus. J’ai essayé d’utiliser void *, mais une solution pourrait peut-être exister en utilisant une adresse mémoire et ensuite accéder à un membre de la structure sans transtyper en types spécifiques. Je vais poser cette question dans une autre question.

    C’est encore un autre pointeur sur lequel il faut d’abord apprendre les bases. La chose qui vous manque s’appelle ‘déclaration anticipée’:

     struct chicken; // here we tell the comstackr that 'struct chicken' is a thing struct egg{ struct chicken *laidby; // while the comstackr 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; }; 

    Ce qui me manque, c’est la possibilité d’appeler la super méthode à partir de la méthode d’exécution substituée d’une manière ou d’une autre?

    Ce ne sont pas des méthodes et il n’y a pas de substitution. Dans votre code, aucune POO ne se produit, C est un langage de programmation procédural. Bien qu’il existe des extensions POO pour le langage C, vous ne devriez vraiment pas y aller sans connaître les bases du langage C.