Question

J'ai longtemps pensé qu'en C, toutes les variables devaient être déclarées au début de la fonction. Je sais que dans C99, les règles sont les mêmes que dans C ++, mais quelles sont les règles de placement de déclaration de variable pour C89 / ANSI C?

Le code suivant est compilé avec succès avec gcc -std = c89 et gcc -ansi :

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

Les déclarations de c et de s ne doivent-elles pas provoquer une erreur en mode C89 / ANSI?

Était-ce utile?

La solution

Il compile avec succès car GCC le permet en tant qu’extension GNU, même si cela ne fait pas partie de la norme C89 ou ANSI. Si vous souhaitez vous conformer strictement à ces normes, vous devez passer le drapeau -pedantic .

Autres conseils

Pour C89, vous devez déclarer toutes vos variables au début d'un bloc d'étendue .

Ainsi, votre déclaration char c est valide car elle se trouve en haut du bloc de portée de la boucle for. Cependant, la déclaration char * s doit être une erreur.

Le regroupement des déclarations de variable en haut du bloc est un héritage probablement dû aux limitations d'anciens compilateurs C primitifs. Toutes les langues modernes recommandent et parfois même imposent la déclaration des variables locales au dernier point: l'endroit où elles sont initialisées. Parce que cela supprime le risque d'utiliser une valeur aléatoire par erreur. La séparation de la déclaration et de l'initialisation vous empêche également d'utiliser & const; quot " (ou "final") quand vous le pouvez.

Malheureusement, C ++ continue d’accepter l’ancienne méthode de déclaration la plus ancienne pour la compatibilité ascendante avec le C (un glisser-déposer de la compatibilité C entre de nombreux autres ...). Mais C ++ essaie de s’écarter de ce dernier:

  • La conception des références C ++ ne permet même pas un tel regroupement de blocs.
  • Si vous séparez la déclaration et l'initialisation d'un objet local C ++ , vous ne payez rien du coût d'un constructeur supplémentaire. Si le constructeur no-arg n'existe pas, vous n'êtes même pas autorisé à séparer les deux!

C99 commence à déplacer C dans cette même direction.

Si vous craignez de ne pas trouver où les variables locales sont déclarées, cela signifie que vous avez un problème beaucoup plus important: le bloc englobant est trop long et doit être scindé.

https : //www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

Du point de vue de la maintenabilité plutôt que de la syntaxe, il existe au moins trois courants de pensée:

  1. Déclarez toutes les variables au début de la fonction afin qu'elles soient au même endroit et que vous puissiez voir la liste complète d'un coup d'œil.

  2. Déclarez toutes les variables aussi près que possible du lieu où elles ont été utilisées pour la première fois. Ainsi, vous saurez pourquoi chacune d’elles est nécessaire.

  3. Déclarez toutes les variables au début du bloc le plus interne, de sorte qu'elles sortent du domaine dès que possible et permettent au compilateur d'optimiser la mémoire et de vous avertir si vous les utilisez par inadvertance là où vous ne les aviez pas prévu.

Je préfère généralement la première option, car je trouve que les autres me forcent souvent à rechercher le code pour les déclarations. Définir toutes les variables à l’avance facilite également leur initialisation et leur affichage à partir d’un débogueur.

Je déclare parfois des variables dans un bloc de portée plus petit, mais uniquement pour une bonne raison, pour laquelle j'en ai très peu. Un exemple pourrait être après un fork () , pour déclarer des variables nécessaires uniquement au processus enfant. Pour moi, cet indicateur visuel est un rappel utile de leur objectif.

Comme l'ont noté d'autres personnes, GCC est permissif à cet égard (et éventuellement d'autres compilateurs, en fonction des arguments avec lesquels ils s'appellent) même en mode 'C89', à moins que vous n'utilisiez la vérification 'pédante'. Pour être honnête, il n’ya pas beaucoup de bonnes raisons de ne pas avoir le pédantisme; Un code moderne de qualité doit toujours être compilé sans avertissements (ou très peu de cas où vous savez que vous faites quelque chose de suspect pouvant être considéré comme une erreur possible par le compilateur). Par conséquent, si vous ne pouvez pas compiler votre code avec une configuration pédante, il devra probablement faire l'objet d'une certaine attention.

C89 exige que les variables soient déclarées avant toute autre instruction dans chaque portée. Les normes ultérieures permettent une déclaration plus proche de l’utilisation (ce qui peut être à la fois plus intuitif et plus efficace), notamment la déclaration et l’initialisation simultanées d’une variable de contrôle de boucle dans 'for 'boucles.

Comme cela a été noté, il existe deux écoles de pensée à ce sujet.

1) Déclarez tout en haut des fonctions car nous sommes en 1987.

2) Déclarez le plus proche de la première utilisation et dans le plus petit champ possible.

Ma réponse à cette question est de faire les deux! Laissez-moi vous expliquer:

Pour les fonctions longues, 1) rend le refactoring très difficile. Si vous travaillez dans une base de code où les développeurs sont contre l'idée de sous-routines, vous aurez 50 déclarations de variable au début de la fonction et certaines d'entre elles pourraient simplement être un "i". pour une boucle for qui est tout en bas de la fonction.

J’ai donc développé cette déclaration et essayé de faire l’option 2) religieusement.

Je suis revenu à l'option 1 à cause d'une chose: les fonctions courtes. Si vos fonctions sont suffisamment courtes, vous aurez peu de variables locales et, comme elles sont courtes, si vous les mettez en haut de la fonction, elles seront toujours proches de la première utilisation.

De même, l'anti-motif de "déclarer et définir sur NULL". lorsque vous souhaitez déclarer au sommet mais que vous n'avez pas effectué les calculs nécessaires à l'initialisation, le problème est résolu car les éléments à initialiser seront probablement reçus sous forme d'arguments.

Alors maintenant, je pense que vous devriez déclarer au sommet des fonctions et le plus près possible de la première utilisation. Alors les deux! Et le moyen de le faire est avec des sous-programmes bien divisés.

Mais si vous travaillez sur une fonction longue, placez les éléments les plus proches de la première utilisation, car ainsi, il sera plus facile d'extraire les méthodes.

Ma recette est la suivante. Pour toutes les variables locales, prenez la variable et déplacez sa déclaration en bas, compilez, puis déplacez la déclaration juste avant l'erreur de compilation. C'est la première utilisation. Faites cela pour toutes les variables locales.

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Maintenant, définissez un bloc de portée qui commence avant la déclaration et déplacez la fin jusqu'à la compilation du programme

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

Cela ne compile pas car il y a un peu plus de code qui utilise foo. Nous pouvons remarquer que le compilateur a pu consulter le code qui utilise bar car il n’utilise pas foo. À ce stade, il y a deux choix. La mécanique consiste simplement à déplacer le "} " jusqu’à ce qu’il soit compilé, l’autre option consiste à inspecter le code et à déterminer si l’ordre peut être modifié en:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

Si l'ordre peut être modifié, c'est probablement ce que vous voulez, car cela raccourcit la durée de vie des valeurs temporaires.

Autre chose à noter, la valeur de foo doit-elle être préservée entre les blocs de code qui l'utilisent, ou pourrait-il s'agir simplement d'un foo différent dans les deux cas? Par exemple

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

Ces situations nécessitent plus que ma procédure. Le développeur devra analyser le code pour savoir quoi faire.

Mais la première étape consiste à trouver le premier usage. Vous pouvez le faire visuellement, mais parfois, il est simplement plus facile de supprimer la déclaration, d’essayer de la compiler et de la remettre au-dessus de la première utilisation. Si cette première utilisation est à l'intérieur d'une instruction if, mettez-la là et vérifiez si elle compile. Le compilateur identifiera ensuite d'autres utilisations. Essayez de créer un bloc d’étendue qui englobe les deux utilisations.

Une fois cette partie mécanique terminée, il devient alors plus facile d'analyser l'emplacement des données. Si une variable est utilisée dans un bloc de grande portée, analysez la situation et voyez si vous utilisez simplement la même variable pour deux choses différentes (comme un "i" utilisé pour deux boucles for). Si les utilisations ne sont pas liées, créez de nouvelles variables pour chacune de ces utilisations.

Je citerai quelques affirmations du manuel de la version 4.7.0 de gcc pour une explication claire.

"Le compilateur peut accepter plusieurs normes de base, telles que" c90 "ou" c ++ 98 ", ainsi que les dialectes GNU de ces normes, tels que" gnu90 "ou" gnu ++ 98 ". En spécifiant une norme de base, le compilateur acceptera tous les programmes respectant cette norme et ceux utilisant des extensions GNU qui ne la contredisent pas. Par exemple, '-std = c90' désactive certaines fonctionnalités de GCC incompatibles avec ISO C90, telles que les mots clés asm et typeof, mais pas les autres extensions GNU n'ayant pas de signification dans ISO C90, telles que la suppression du milieu. terme de?: expression. "

Je pense que le point clé de votre question est la suivante: pourquoi gcc ne se conforme-t-il pas à C89 même si l'option "-std = c89" est utilisé. Je ne connais pas la version de votre gcc, mais je pense qu'il n'y aura pas de grande différence. Le développeur de gcc nous a indiqué que l’option "-std = c89" signifie simplement que les extensions qui contredisent C89 sont désactivées. Cela n’a donc rien à voir avec des extensions qui n’ont pas de sens en C89. Et l'extension qui ne restreint pas le placement de la déclaration de variable appartient aux extensions qui ne contredisent pas C89.

Pour être honnête, tout le monde pensera que C89 doit être entièrement conforme à l’option "-std = c89". Mais ce n'est pas le cas. Pour ce qui est du problème, déclarer que toutes les variables au début est meilleure ou pire n’est qu’une question d’habitude.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top