38 votes

Existe-t-il un moyen de faire du curry en C ?

Disons que j'ai un pointeur sur une fonction _stack_push(stack* stk, void* el) . Je veux pouvoir appeler curry(_stack_push, my_stack) et obtenir en retour une fonction qui prend juste void* el . Je n'ai pas trouvé de moyen de le faire, puisque le langage C ne permet pas de définir des fonctions en cours d'exécution, mais je sais qu'il y a des gens bien plus intelligents que moi ici :). Des idées ?

21voto

Jared Oberhaus Points 8877

J'ai trouvé un article de Laurent Dami qui traite du curry en C/C++/Objective-C :

Plus de réutilisation fonctionnelle en C/C++/Objective-c avec les fonctions curries

Il est intéressant de savoir comment il est mis en œuvre en C :

Notre implémentation actuelle utilise des constructions C existantes pour ajouter le mécanisme de curry. C'était beaucoup plus facile à faire que de modifier le compilateur, et c'est suffisant pour prouver l'intérêt du curry. Cette approche présente toutefois deux inconvénients. Tout d'abord, les fonctions curry ne peuvent pas être vérifiées au niveau du type, et doivent donc être utilisées avec précaution afin d'éviter les erreurs. D'autre part, la fonction curry ne peut pas connaître la taille de ses arguments, et les compte comme s'ils étaient tous de la taille d'un entier.

Le document ne contient pas de mise en œuvre de curry() mais vous pouvez imaginer comment il est mis en œuvre à l'aide de pointeurs de fonction y fonctions variadiques .

9 votes

+1 belle découverte, et j'aime bien "Bien que nous n'ayons pas effectué de tests approfondis, nous pouvons estimer qu'un appel de fonction curry est environ 60 fois plus lent qu'un appel de fonction normal".

6 votes

(Je l'aime bien parce que parfois on a besoin de quelque chose de très urgent, et une solution qui fonctionne seulement 60 fois plus lentement est infiniment mieux que pas de solution du tout).

0 votes

Merci, j'ai trouvé le lien sur la page Wikipedia "Currying" : fr.wikipedia.org/wiki/Curried_function

8voto

user1235651 Points 41

GCC fournit une extension pour la définition de fonctions imbriquées. Bien qu'il ne s'agisse pas de la norme ISO C, cette extension peut présenter un certain intérêt, car elle permet de répondre à la question de manière assez pratique. En bref, une fonction imbriquée peut accéder aux variables locales de la fonction mère et les pointeurs vers ces variables peuvent également être renvoyés par la fonction mère.

Voici un exemple court et explicite :

#include <stdio.h>

typedef int (*two_var_func) (int, int);
typedef int (*one_var_func) (int);

int add_int (int a, int b) {
    return a+b;
}

one_var_func partial (two_var_func f, int a) {
    int g (int b) {
        return f (a, b);
    }
    return g;
}

int main (void) {
    int a = 1;
    int b = 2;
    printf ("%d\n", add_int (a, b));
    printf ("%d\n", partial (add_int, a) (b));
}

Il y a cependant une limite à cette construction. Si vous gardez un pointeur sur la fonction résultante, comme dans

one_var_func u = partial (add_int, a);

l'appel de fonction u(0) peut entraîner un comportement inattendu, car la variable a que u lit a été détruite juste après partial résilié.

Voir cette section de la documentation du GCC .

12 votes

Dans le manuel (sous le lien que vous avez fourni) : "Si vous essayez d'appeler la fonction imbriquée par le biais de son adresse après la sortie de la fonction qui la contient, l'enfer va se déchaîner ."

0 votes

Si vous vous limitez déjà à GCC, vous pouvez utiliser des expressions de déclaration pour retarder l'enfer jusqu'à ce que la fonction appelante se termine (c'est-à-dire que cela fonctionnera pour tout sauf pour les callbacks asynchrones) : gist.github.com/a3f/2729c1248d0f2ee39b4a

4voto

ChrisW Points 37322

Voici ma première hypothèse (ce n'est peut-être pas la meilleure solution).

La fonction curry pourrait allouer de la mémoire sur le tas et placer les valeurs des paramètres dans cette mémoire allouée sur le tas. L'astuce consiste alors à faire en sorte que la fonction retournée sache qu'elle est censée lire ses paramètres à partir de cette mémoire allouée au tas. S'il n'y a qu'une seule instance de la fonction retournée, alors un pointeur sur ces paramètres peut être stocké dans un singleton/global. Sinon, s'il y a plus d'une instance de la fonction retournée, je pense que curry doit créer chaque instance de la fonction retournée dans la mémoire allouée au tas (en écrivant des opcodes tels que "get that pointer to the parameters", "push the parameters", et "invoke that other function" dans la mémoire allouée au tas). Dans ce cas, vous devez vous assurer que la mémoire allouée est exécutable, et peut-être (je ne sais pas) même craindre les programmes antivirus.

1voto

Basile Starynkevitch Points 67055

puisque le langage C ne permet pas de définir des fonctions au moment de l'exécution.

C'est en principe le cas en norme C . Lire n1570 pour plus de détails.

Cependant, en pratique il peut être faux. Considérons

  • sur les systèmes POSIX (par exemple Linux), générer du code C au moment de l'exécution dans un fichier temporaire /tmp/generated1234.c qui définit quelques void genfoo1234(void) la compilation de ce fichier (par exemple à l'aide d'une version récente de la fonction CCG en tant que gcc -O -fPIC -Wall -shared /tmp/generated1234.c -o /tmp/generated1234.so ), puis en utilisant dlopen(3) en /tmp/generated1234.so puis dlsym(3) en genfoo1234 sur la poignée renvoyée par dlopen pour obtenir un pointeur de fonction). D'après mon expérience personnelle, une telle approche est aujourd'hui (en 2021, sur des ordinateurs portables Linux) suffisamment rapide même pour une utilisation interactive (si chaque fichier C généré temporairement contient quelques centaines de lignes de code C).

  • sur les processeurs x86, x86-64, ARM en utilisant une bibliothèque de génération de code machine telle que L'éclair GNU , libgccjit (ou en C++, asmjit )

Dans la pratique, vous générez le code d'un fermeture (regroupement d'un pointeur de fonction avec des valeurs fermées) et l'utiliser en tant que rappel .

Un point connexe est le ramassage des ordures. manuel sur la collecte des ordures ménagères .

Envisagez également d'intégrer un interprète existant dans votre application, comme Lua , GNU guile , Python , etc....

Étudiez, au moins pour vous en inspirer, le code source de ces interprètes.

Le livre de Quenniec Lisp en petits morceaux et le Livre du dragon valent la peine d'être lues. Tous deux expliquent des questions pratiques et des détails de mise en œuvre

Voir aussi le __builtin_call_with_static_chain dans les compilateurs GCC récents (en 2021).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X