web-dev-qa-db-fra.com

Fonctions anonymes utilisant des expressions d'instruction GCC

Cette question n'est pas très spécifique. C'est vraiment pour mon propre enrichissement en C et j'espère que d'autres pourront le trouver utile aussi.

Déni de responsabilité: Je sais que beaucoup auront l’impulsion de répondre en disant "si vous essayez de faire FP, utilisez simplement un langage fonctionnel". Je travaille dans un environnement intégré qui doit être lié à de nombreuses autres bibliothèques C, ne dispose pas de beaucoup d'espace pour de nombreuses autres bibliothèques partagées et ne prend pas en charge de nombreuses exécutions de langage. De plus, l’allocation dynamique de mémoire est hors de question. Je suis aussi juste très curieux.

Beaucoup d’entre nous ont vu cette jolie macro C pour les expressions lambda:

#define lambda(return_type, function_body) \
({ \
      return_type __fn__ function_body \
          __fn__; \
})

Et un exemple d'utilisation est:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });
max(4, 5); // Example

En utilisant gcc -std=c89 -E test.c, le lambda se développe pour:

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

Alors, voici mes questions:

  1. Que fait exactement la ligne int (* X)? déclarer? Bien sûr, int * X; est un pointeur sur un entier, mais en quoi ces deux éléments diffèrent-ils?

  2. En regardant la macro exapnded, que fait le __fn__ final? Si j’écris une fonction de test void test() { printf("hello"); } test; - une erreur est immédiatement générée. Je ne comprends pas cette syntaxe.

  3. Qu'est-ce que cela signifie pour le débogage? (Je projette d'expérimenter moi-même cela et gdb, mais les expériences ou les opinions des autres seraient formidables). Est-ce que cela bousillerait les analyseurs statiques?

46
B. VB.

Cette déclaration (à la portée du bloc):

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

n'est pas C mais est valide GNU C.

Il utilise deux extensions gcc:

  1. fonctions imbriquées
  2. expressions de déclaration

Les deux fonctions nested (définissant une fonction dans une instruction composée) et les expressions statement (({}), un bloc générant une valeur) ne sont pas autorisées en C et proviennent de GNU C.

Dans une expression de déclaration, la dernière déclaration d'expression est la valeur de la construction. C'est pourquoi la fonction imbriquée __fn__ apparaît sous la forme d'une instruction d'expression à la fin de celle-ci. Un indicateur de fonction (__fn__ dans la dernière instruction d'expression) d'une expression est converti en un pointeur sur une fonction par les conversions habituelles. C'est la valeur utilisée pour initialiser la fonction pointeur max.

38
ouah

Votre macro lambda exploite deux fonctions géniales. Premièrement, il utilise des fonctions imbriquées pour réellement définir le corps de votre fonction (votre lambda n’est donc pas vraiment anonyme; elle utilise simplement une variable implicite __fn__ (qui devrait être renommée autrement, comme le sont les noms de caractères doubles soulignés réservé au compilateur, alors peut-être que quelque chose comme yourapp__fn__ serait mieux).

Tout cela est lui-même exécuté dans une instruction composée GCC (voir http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs ), dont le format de base ressemble à quelque chose comme: :

({ ...; retval; })

la dernière instruction de l'instruction composée étant l'adresse de la fonction qui vient d'être déclarée. Maintenant, int (*max)(int,int) se voit simplement attribuer la valeur de l'instruction composée, qui est maintenant le pointeur sur la fonction 'anonyme' qui vient d'être déclarée.

Les macros de débogage sont une douleur royale bien sûr.

En ce qui concerne la raison pour laquelle test; .. au moins ici, le test est redéclaré comme type de symbole différent, ce qui signifie que GCC le traite comme une déclaration et non comme une expression (inutile). Parce que les variables non typées sont définies par défaut sur int et que vous avez déjà déclaré test en tant que fonction (essentiellement, void (*)(void)), vous obtenez cela .. mais je peux me tromper à ce sujet.

Ce n'est pas portable par aucun effort d'imagination cependant.

6
Mark Nunberg

Réponse partielle: Ce n'est pas int (* X) qui vous intéresse. C'est int (* X) (y, z). C’est un pointeur de fonction sur la fonction appelée X qui prend (y, z) et renvoie int.

Pour le débogage, ce sera vraiment difficile. La plupart des débogueurs ne peuvent pas suivre via une macro. Vous devrez probablement déboguer l’Assemblée.

1
Steve Rowe
  1. int (*max)(int, int) est le type de variable que vous déclarez. Il est défini comme un pointeur de fonction nommé max qui renvoie int et prend deux ints en tant que paramètres.

  2. __fn__ fait référence au nom de la fonction, qui dans ce cas est max.

  3. Je n'ai pas de réponse ici. J'imagine que vous pouvez le parcourir si vous l'avez exécuté via le préprocesseur.

0
gcochard