Quelle est l'utilisation idiomatique de blocs arbitraires en C?


15

Un bloc est une liste d'instructions à exécuter. Les exemples où les blocs apparaissent en C sont après une instruction while et dans les instructions if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C permet également à un bloc d'être imbriqué dans un bloc. Je peux l'utiliser pour réutiliser les noms de variables, supposons que j'aime vraiment 'x'

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

imprimera le nombre 5 dix fois. Je suppose que je pourrais voir des situations où garder le nombre de noms de variables bas est souhaitable. Peut-être en expansion macro. Cependant, je ne vois aucune raison sérieuse d'avoir besoin de cette fonctionnalité. Quelqu'un peut-il m'aider à comprendre les utilisations de cette fonctionnalité en fournissant des idiomes où elle est utilisée.


1
Honnêtement, j'essaie juste de comprendre la syntaxe C, et je suis curieux.
Jonathan Gallagher du

L'esprit de C est de faire confiance au programmeur. Le programmeur a le pouvoir de faire quelque chose de grand ... ou de faire quelque chose de terrible. Personnellement, je n'aime pas les noms de variables surchargés mais un autre programmeur peut le faire. À la fin de la journée, si nous accomplissons ce que nous sommes censés faire avec un minimum de bugs ... pourquoi devrions-nous discuter?
Fiddling Bits

1
C'est le sentiment que j'obtiens de C. Je suis tout à fait pour la syntaxe qui prend en charge différents styles de codage (ainsi tant que la sémantique du langage est correcte). C'est juste que ... J'ai vu cela, et ma réponse immédiate a été, je pouvais appliquer une transformation source à source renommant toutes les variables dans un bloc avec de nouveaux noms, et aplatir complètement le bloc. Chaque fois que je pense que je pourrais me débarrasser de quelque chose, je suppose qu'il y a quelque chose que j'ai manqué.
Jonathan Gallagher

Chronométrage drôle, en tant que programmeur non-C, je suis tombé sur cette syntaxe aujourd'hui dans le code C et j'étais curieux de savoir à quoi cela servait. Je suis ravi que vous posiez cette question.
Brandon

Réponses:


8

L'idée n'est pas de limiter le nombre de noms de variables ou d'encourager la réutilisation des noms, mais plutôt de limiter la portée des variables. Si tu as:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

alors la portée de yest limitée au bloc, ce qui signifie que vous pouvez l'oublier avant ou après le bloc. Vous voyez cela utilisé le plus souvent en relation avec les boucles et les conditions. Vous le voyez également plus souvent dans les langages de type C tels que C ++, où une variable hors de portée entraîne sa destruction.


Je pense que le bloc se produit naturellement avec les conditionnels, les boucles et les corps de fonction. Ce qui m'intéresse, c'est qu'en C, je peux placer un bloc n'importe où. Votre deuxième commentaire sur C ++ est intéressant: laisser une portée entraîne la destruction. S'agit-il uniquement de la récupération de place, ou s'agit-il d'une autre utilisation: pourrais-je prendre un corps de fonction, qui a des "sections" distinctes et utiliser des blocs pour contrôler l'empreinte mémoire en déclenchant la destruction de l'espace variable?
Jonathan Gallagher

1
Pour autant que je sache, C préallouera toute la mémoire pour toutes les variables de la fonction. Le fait qu'ils quittent la portée à mi-chemin via une fonction n'aura aucun avantage en
termes de

@Gankro Cela peut avoir un impact s'il existe plusieurs étendues imbriquées exclusives. Un compilateur peut réutiliser un morceau de mémoire préalloué unique pour les variables dans chacune de ces étendues. Bien sûr, la raison pour laquelle cela ne vient pas à l'esprit est que si une seule étendue arbitraire est déjà un signe que vous devez probablement extraire vers une fonction, deux ou plusieurs étendues arbitraires sont certainement une bonne indication que vous devez refactoriser. Pourtant, il apparaît comme une solution sensée dans des choses comme les blocs de boîtier de commutation de temps en temps.
TNE

16

Parce que dans les temps anciens de C, de nouvelles variables ne pouvaient être déclarées que dans un nouveau bloc.

De cette façon, les programmeurs pourraient introduire de nouvelles variables au milieu d'une fonction sans la divulguer et en minimisant l'utilisation de la pile.

Avec les optimiseurs d'aujourd'hui, c'est inutile et c'est un signe que vous devez envisager d'extraire le bloc dans sa propre fonction.

Dans une instruction switch, il est utile de placer les cas dans leurs propres blocs pour éviter la double déclaration.

En C ++, il est très utile, par exemple, pour les gardes de verrouillage RAII et pour garantir que les destructeurs exécutent le verrou de libération lorsque l'exécution sort du cadre et continuent de faire d'autres choses en dehors de la section critique.


2
+1 pour les gardes de sécurité RAII. Cela étant dit, ce même concept ne pourrait-il pas également être utile pour la désallocation de tampon de pile de grande taille dans certaines parties d'une routine? Je ne l'ai jamais fait, mais cela ressemble à quelque chose qui pourrait certainement se produire dans du code intégré ...
J Trana

2

Je ne le considérerais pas comme des blocs "arbitraires". Ce n'est pas une fonctionnalité tant destinée aux développeurs, mais la façon dont C utilise les blocs permet d'utiliser la même construction de blocs à plusieurs endroits avec la même sémantique. Un bloc (en C) est une nouvelle portée, et les variables qui le quittent sont éliminées. Ceci est uniforme quelle que soit la façon dont le bloc est utilisé.

Dans d'autres langues, ce n'est pas le cas. Cela a l'avantage de permettre moins d'abus comme vous le montrez, mais l'inconvénient que les blocs se comportent différemment selon le contexte dans lequel ils se trouvent.

J'ai rarement vu des blocs autonomes utilisés en C ou C ++ - souvent quand il y a une grande structure ou un objet qui représente une connexion ou quelque chose dont vous voulez forcer la destruction. Habituellement, cela indique que votre fonction fait trop de choses et / ou est trop longue.


1
Malheureusement, je vois régulièrement des blocs autonomes - dans presque tous les cas parce que la fonction fait trop et / ou est trop longue.
mattnz

Je vois et écris régulièrement des blocs autonomes en C ++ mais uniquement pour forcer la destruction des wrappers autour des verrous et autres ressources partagées.
J Trana

2

Vous devez comprendre que les principes de programmation qui semblent maintenant évidents ne l'ont pas toujours été. Les meilleures pratiques C dépendent en grande partie de l'âge de vos exemples. Lorsque C a été introduit pour la première fois, la division de votre code en petites fonctions était considérée comme trop inefficace. Dennis Ritchie a essentiellement menti et a déclaré que les appels de fonction étaient vraiment efficaces en C (ce n'était pas le cas à l'époque), ce qui a incité les gens à les utiliser davantage, bien que les programmeurs C n'aient jamais vraiment dépassé complètement une culture d'optimisation prématurée.

Il est une bonne pratique de programmation, aujourd'hui encore , de limiter la portée de vos variables aussi faible que possible. De nos jours, nous le faisons généralement en créant une nouvelle fonction, mais si les fonctions sont considérées comme coûteuses, l'introduction d'un nouveau bloc est un moyen logique de limiter votre portée sans la surcharge d'un appel de fonction.

Cependant, j'ai commencé à programmer en C il y a plus de 20 ans, à l'époque où vous deviez déclarer toutes vos variables en haut d'une étendue, et je ne me souviens pas que l'observation de variables comme celle-là ait jamais été considérée comme un bon style. Redéclarer en deux blocs l'un après l'autre comme dans une instruction switch, oui, mais pas d'observation. Peut - être que si la variable était déjà utilisée et que le nom spécifique était très idiomatique pour l'API que vous appelez, comme destet srcdans strcpy, par exemple.


1

Les blocs arbitraires sont utiles pour introduire des variables intermédiaires qui ne sont utilisées que dans des cas particuliers de calcul.

Il s'agit d'un modèle courant dans le calcul scientifique, où les procédures numériques généralement:

  1. s'appuyer sur un grand nombre de paramètres ou de quantités intermédiaires;
  2. doivent faire face à de nombreux cas particuliers.

En raison du deuxième point, il est utile d'introduire des variables temporaires de portée limitée, ce qui est obtenu soit en utilisant un bloc arbitraire, soit en introduisant une fonction auxiliaire.

Bien que l'introduction d'une fonction auxiliaire puisse ressembler à une évidence ou à une meilleure pratique à suivre aveuglément, il y a en fait peu d'avantages à le faire dans cette situation particulière.

Parce qu'il y a beaucoup de paramètres et de quantités intermédiaires, nous voulons introduire une structure pour les passer à la fonction auxiliaire.

Mais, comme nous voulons être conséquents avec nos pratiques, nous n'introduirons pas seulement une fonction auxiliaire mais plusieurs. Donc, si nous introduisons des structures ad-hoc véhiculant des paramètres pour chaque fonction, ce qui introduit beaucoup de surcharge de code pour déplacer les paramètres d'avant en arrière, ou nous en introduisons un qui les gouvernera toute la structure de la feuille de calcul, qui contient toutes nos variables mais ressemble à un grappin de bits sans consistance, où à tout moment seule la moitié des paramètres a une signification intéressante.

Par conséquent, ces structures auxiliaires sont généralement encombrantes et les utiliser signifie choisir entre une surcharge de code ou introduire une abstraction dont la portée est trop large et affaiblir le sens du programme, au lieu de le renforcer .

L'introduction de fonctions auxiliaires pourrait faciliter les tests unitaires du programme en introduisant une granularité de test plus fine, mais en combinant les tests unitaires pour ne pas que les procédures de bas niveau et les tests de régression sous forme de comparaisons (avec numdiff) des traces numériques des procédures font un travail tout aussi bon .


0

En général, la réutilisation des noms de variables de cette manière provoque trop de confusion pour les futurs lecteurs de votre code. Il vaut mieux simplement nommer la variable interne autre chose. En fait, le langage C # interdit spécifiquement l'utilisation de variables de cette manière.

Je pouvais voir comment l'utilisation de blocs imbriqués dans les extensions de macro serait utile pour empêcher les collisions de noms de variables.


0

C'est pour délimiter des choses qui n'ont normalement pas de portée à ce niveau. Ceci est extrêmement rare, et en général, le refactoring est un meilleur choix, mais je l'ai rencontré une ou deux fois dans le passé dans les instructions switch:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

Lorsque vous envisagez la maintenance, la refactorisation doit être équilibrée avec toutes les implémentations d'affilée, tant que chacune ne comporte que quelques lignes. Il peut être plus facile de les avoir tous ensemble, plutôt qu'une liste de noms de fonctions.

Dans mon cas, si je me souviens bien, la plupart des cas étaient de légères variations du même code - sauf que l'un d'entre eux était identique à un autre, juste avec un prétraitement supplémentaire. Un commutateur a fini par être la forme la plus simple à prendre pour le code, et la portée supplémentaire a permis l'utilisation de variables locales supplémentaires dont lui et d'autres cas n'avaient pas à s'inquiéter (tels que les noms de variables se chevauchant accidentellement avec un défini avant le commutateur, mais utilisé plus tard).

Notez que l'instruction switch est simplement son utilisation que j'ai rencontrée. Je suis sûr que cela peut s'appliquer ailleurs aussi, mais je ne me souviens pas de les avoir utilisés efficacement de cette façon.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.