Les fonctions nestedes sont-elles une mauvaise chose dans gcc?

Je sais que les fonctions nestedes ne font pas partie du standard C, mais comme elles sont présentes dans gcc (et que gcc est le seul compilateur qui m’intéresse), j’ai tendance à les utiliser assez souvent.

Est-ce une mauvaise chose ? Si oui, pourriez-vous me montrer des exemples méchants? Quel est le statut des fonctions nestedes dans gcc? Vont-ils être enlevés?

Les fonctions nestedes ne font vraiment rien que vous ne puissiez faire avec des fonctions non nestedes (c’est pourquoi ni le C ni le C ++ ne les fournissent). Vous dites que vous n’êtes pas intéressé par les autres compilateurs – eh bien, c’est peut-être vrai en ce moment, mais qui sait ce que l’avenir apportera? Je les éviterais, ainsi que toutes les autres “améliorations” de GCC.

Une petite histoire pour illustrer cela – j’avais l’habitude de travailler pour un Polytechinc britannique qui utilisait principalement des boîtes DEC – en particulier un DEC-10 et du VAXen. Toutes les facultés d’ingénierie ont utilisé les nombreuses extensions DEC de FORTRAN dans leur code – elles étaient certaines que nous restrions pour toujours un magasin DEC. Ensuite, nous avons remplacé le DEC-10 par un ordinateur central IBM, le compilateur FORTRAN ne prenant en charge aucune des extensions. Je peux vous dire qu’il y a eu beaucoup de gémissements et de grincements de dents ce jour-là. Mon propre code FORTRAN (un simulateur 8080) a été transféré à IBM en quelques heures (presque tout occupé à apprendre à utiliser le compilateur IBM), car je l’avais écrit dans le standard FORTRAN-77.

Parfois, des fonctions nestedes peuvent être utiles, en particulier avec des algorithmes qui mélangent de nombreuses variables. Quelque chose comme un type de fusion écrit à 4 voies pourrait nécessiter de conserver un grand nombre de variables locales et d’avoir plusieurs morceaux de code répétés qui en utilisent beaucoup. Pour appeler ces bits de code répétés en tant que routine auxiliaire externe, il faudrait transmettre un grand nombre de parameters et / ou faire en sorte que la routine auxiliaire y accède via un autre niveau d’indirection de pointeur.

Dans de telles circonstances, je peux imaginer que les routines nestedes permettent une exécution de programme plus efficace que d’autres moyens d’écriture du code, du moins si le compilateur optimise la situation dans laquelle une récursion existante est effectuée en appelant à nouveau la fonction la plus externe; les fonctions en ligne, si l’espace le permet, pourraient être meilleures sur les CPU non mises en cache, mais le code plus compact offert par des routines séparées peut être utile. Si les fonctions internes ne peuvent pas s’appeler ou s’appeler de manière récursive, elles peuvent partager une trame de stack avec la fonction externe et pourraient ainsi accéder à ses variables sans la pénalité de temps d’un déréférencement de pointeur supplémentaire.

Cela dit, j’éviterais d’utiliser des fonctionnalités propres au compilateur, sauf dans les cas où les avantages immédiats l’emportent sur les coûts futurs qui pourraient résulter de la réécriture du code d’une autre manière.

Comme la plupart des techniques de programmation, les fonctions nestedes doivent être utilisées quand et seulement si elles sont appropriées.

Vous n’êtes pas obligé d’utiliser cet aspect, mais si vous le souhaitez, les fonctions nestedes réduisent le besoin de transmettre des parameters en accédant directement aux variables locales de leurs fonctions. C’est pratique. Une utilisation prudente des parameters “invisibles” peut améliorer la lisibilité. Une utilisation négligente peut rendre le code beaucoup plus opaque.

En évitant certains ou tous les parameters, il est plus difficile de réutiliser une fonction nestede ailleurs car toute nouvelle fonction contenant aurait à déclarer ces mêmes variables. La réutilisation est généralement bonne, mais de nombreuses fonctions ne seront jamais réutilisées, ce qui n’a souvent aucune importance.

Comme le type d’une variable est hérité avec son nom, la réutilisation de fonctions nestedes peut vous donner un polymorphism peu coûteux, comme une version limitée et primitive de modèles.

L’utilisation de fonctions nestedes introduit également le risque de bugs si une fonction accède ou modifie involontairement l’une des variables de son conteneur. Imaginez une boucle for contenant un appel à une fonction nestede contenant une boucle for utilisant le même index sans déclaration locale. Si je concevais un langage, j’inclurais des fonctions nestedes mais nécessitais une déclaration “inherit x” ou “inherit const x” pour rendre plus évident ce qui se passait et éviter les inheritances et modifications non voulus.

Il existe plusieurs autres utilisations, mais la fonction la plus importante des fonctions nestedes est d’autoriser les fonctions d’assistance internes qui ne sont pas visibles de l’extérieur, une extension des fonctions static non extern de C ++ et C ++ ou des fonctions privées non publiques de C ++. Avoir deux niveaux d’encapsulation vaut mieux qu’un. Cela permet également de surcharger localement les noms de fonctions, vous n’avez donc pas besoin de longs noms décrivant le type utilisé par chacune.

Il y a des complications internes lorsqu’une fonction contenant enregistre un pointeur sur une fonction contenue et lorsque plusieurs niveaux d’imbrication sont autorisés, mais les rédacteurs de compilateur traitent ces problèmes depuis plus d’un demi-siècle. Il n’y a pas de problèmes techniques rendant plus difficile l’ajout de C ++ que de C, mais les avantages sont moindres.

La portabilité est importante, mais gcc est disponible dans de nombreux environnements et au moins une autre famille de compilateurs prend en charge les fonctions nestedes: IBM xlc disponible sous AIX, Linux sur PowerPC, Linux sur BlueGene, Linux sur Cell et z / OS. Voir http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xmcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htm.

Les fonctions nestedes sont disponibles dans certains nouveaux (par exemple, Python) et dans de nombreux langages plus traditionnels, notamment Ada, Pascal, Fortran, PL / I, PL / IX, Algol et COBOL. C ++ a même deux versions restreintes: les méthodes d’une classe locale peuvent accéder aux variables statiques (mais non automatiques) de la fonction qui le contient, et les méthodes de n’importe quelle classe peuvent accéder aux membres et méthodes de données de la classe statique. Le prochain standard C ++ a des fonctions lamda, qui sont vraiment des fonctions nestedes anonymes. Le monde de la programmation a donc beaucoup d’expérience positive et négative avec eux.

Les fonctions nestedes sont utiles mais faites attention. Utilisez toujours les fonctionnalités et les outils là où ils aident, pas là où ils font mal.

Comme vous l’avez dit, ils sont une mauvaise chose en ce sens qu’ils ne font pas partie de la norme C et, en tant que tels, ne sont pas implémentés par beaucoup (aucun?) D’autres compilateurs C.

N’oubliez pas non plus que g ++ n’implémente pas les fonctions nestedes, vous devrez donc les supprimer si vous avez besoin de récupérer une partie de ce code et de le vider dans un programme C ++.

Les fonctions nestedes peuvent être mauvaises, car dans certaines conditions, le bit de sécurité NX (non-exécuté) sera désactivé. Ces conditions sont:

  • GCC et fonctions nestedes sont utilisées

  • un pointeur sur la fonction nestede est utilisé

  • la fonction nestede accède aux variables de la fonction parent

  • l’architecture offre une protection binary NX (no-execute), par exemple linux 64 bits.

Lorsque les conditions ci-dessus seront remplies, GCC créera un trampoline https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html . Pour prendre en charge les trampolines, la stack sera marquée comme exécutable. voir: https://www.win.tue.nl/~aeb/linux/hh/protection.html

La désactivation du bit de sécurité NX crée plusieurs problèmes de sécurité, le principal étant la protection contre le dépassement de la mémoire tampon est désactivée. En particulier, si un attaquant plaçait du code sur la stack (par exemple, en tant qu’image, tableau ou chaîne paramétrable par l’utilisateur), et qu’un dépassement de tampon se produisait, le code de l’attaquant pourrait alors être exécuté.

En retard au parti, mais je ne suis pas d’accord avec l’affirmation de la réponse acceptée selon laquelle

Les fonctions nestedes ne font vraiment rien que vous ne puissiez pas faire avec des fonctions non nestedes.

Plus précisément:

TL; DR: les fonctions nestedes peuvent réduire l’utilisation de la stack dans les environnements incorporés

Les fonctions nestedes vous permettent d’accéder à des variables à scope lexicale en tant que variables “locales” sans avoir à les placer dans la stack d’appels. Cela peut être très utile lorsque vous travaillez sur un système avec des ressources limitées, par exemple des systèmes intégrés. Considérons cet exemple artificiel:

void do_something(my_obj *obj) { double times2() { return obj->value * 2.0; } double times4() { return times2() * times2(); } ... } 

Notez qu’une fois dans do_something (), à cause des fonctions nestedes, les appels à times2 () et times4 () n’ont besoin de placer aucun paramètre dans la stack, mais retournent des adresses (et des compilateurs intelligents les optimisent même lorsque possible).

Imaginez s’il y avait beaucoup d’état auquel les fonctions internes devaient accéder. Sans fonctions nestedes, tout cet état devrait être transmis sur la stack à chacune des fonctions. Les fonctions nestedes vous permettent d’accéder à l’état comme des variables locales.

Je suis d’accord avec l’exemple de Stefan, et la seule fois où j’ai utilisé des fonctions nestedes (puis je les déclare en inline ), c’est à une occasion similaire.

Je suggérerais également que vous devriez rarement utiliser des fonctions nestedes nestedes, et les rares fois que vous les utilisiez, vous devriez avoir (dans votre esprit et dans certains commentaires) une stratégie pour vous en débarrasser (peut-être même la mettre en œuvre avec #ifdef __GCC__ conditionnel compilation).

Mais GCC étant un compilateur libre (comme dans la parole), cela fait une différence … Et certaines extensions de GCC ont tendance à devenir des normes de facto et sont mises en œuvre par d’autres compilateurs.

Une autre extension de GCC que je trouve très utile est le goto calculé, c’est-à-dire une étiquette sous forme de valeurs . C’est très pratique pour coder des automates ou des interpréteurs de bytecode.

Les fonctions nestedes peuvent être utilisées pour rendre un programme plus facile à lire et à comprendre, en réduisant le nombre de passages de parameters explicites sans introduire beaucoup d’état global.

D’autre part, ils ne sont pas portables pour les autres compilateurs. (Notez les compilateurs, pas les périphériques. Il n’y a pas beaucoup d’endroits où gcc ne fonctionne pas).

Donc, si vous voyez un endroit où vous pouvez rendre votre programme plus clair en utilisant une fonction nestede, vous devez vous demander: “Optimise-t-il la portabilité ou la lisibilité”?

Je suis juste en train d’explorer un type d’utilisation un peu différent des fonctions nestedes. En tant qu’approche pour «évaluation paresseuse» en C.

Imaginez un tel code:

 void vars() { bool b0 = code0; // do something expensive or to ugly to put into if statement bool b1 = code1; if (b0) do_something0(); else if (b1) do_something1(); } 

contre

 void funcs() { bool b0() { return code0; } bool b1() { return code1; } if (b0()) do_something0(); else if (b1()) do_something1(); } 

De cette façon, vous obtenez une clarté (enfin, cela peut être un peu déroutant de voir un tel code pour la première fois) alors que le code est toujours exécuté quand et seulement si nécessaire. En même temps, il est assez simple de le reconvertir en version originale.

Un problème se pose ici si la même “valeur” est utilisée plusieurs fois. GCC a été en mesure d’optimiser l’appel unique lorsque toutes les valeurs sont connues au moment de la compilation, mais je suppose que cela ne fonctionnerait pas pour les appels de fonctions non sortingviaux ou autres. Dans ce cas, la “mise en cache” peut être utilisée, mais cela ajoute à la non lisibilité.

J’ai besoin de fonctions nestedes pour me permettre d’utiliser le code utilitaire en dehors d’un object.

J’ai des objects qui traitent de divers périphériques. Ce sont des structures qui sont transmises par pointeur en tant que parameters aux fonctions membres, comme cela se produit automatiquement en c ++.

Donc je pourrais avoir

  static int ThisDeviceTestBram( ThisDeviceType *pdev ) { int read( int addr ) { return( ThisDevice->read( pdev, addr ); } void write( int addr, int data ) ( ThisDevice->write( pdev, addr, data ); } GenericTestBram( read, write, pdev->BramSize( pdev ) ); } 

GenericTestBram ignore et ne peut pas savoir l’existence de ThisDevice, qui comporte plusieurs instanciations. Mais tout ce dont il a besoin est un moyen de lecture et d’écriture, ainsi qu’une taille. ThisDevice-> read (…) et ThisDevice-> Write (…) ont besoin du pointeur sur ThisDeviceType pour obtenir des informations sur la lecture et l’écriture de la mémoire de bloc (Bram) de cette instanciation particulière. Le pointeur, pdev, ne peut pas avoir de scobe global, car il existe plusieurs instanciations, qui peuvent être exécutées simultanément. Étant donné que l’access se fait via une interface FPGA, la question de la transmission d’une adresse n’est pas simple. Elle varie d’un périphérique à l’autre.

Le code GenericTestBram est une fonction utilitaire:

  int GenericTestBram( int ( * read )( int addr ), void ( * write )( int addr, int data ), int size ) { // Do the test } 

Le code de test n’a donc besoin d’être écrit qu’une seule fois et n’a pas besoin de connaître les détails de la structure du dispositif appelant.

Même avec GCC, cependant, vous ne pouvez pas faire cela. Le problème est le pointeur hors de scope, le problème même devait être résolu. Le seul moyen que je connaisse pour rendre f (x, …) conscient de son parent est de passer un paramètre avec une valeur hors limites:

  static int f( int x ) { static ThisType *p = NULL; if ( x < 0 ) { p = ( ThisType* -x ); } else { return( p->field ); } } return( whatever ); 

La fonction f peut être initialisée par quelque chose qui a le pointeur, puis être appelée de n’importe où. Pas idéal cependant.

Les fonctions nestedes sont un MUST dans tout langage de programmation sérieux.

Sans eux, le sens réel des fonctions n’est pas utilisable.

C’est ce qu’on appelle la scope lexicale.