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 se compile 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 s
provoquer une erreur en mode C89 / ANSI?
c
declaration
c89
mcjabberz
la source
la source
Réponses:
Il se compile avec succès parce que GCC autorise la déclaration de en
s
tant qu'extension GNU, même si elle ne fait pas partie du standard C89 ou ANSI. Si vous souhaitez adhérer strictement à ces normes, vous devez passer le-pedantic
drapeau.La déclaration de
c
au début d'un{ }
bloc fait partie de la norme C89; le bloc n'a pas besoin d'être une fonction.la source
s
est une extension (du point de vue C89). La déclaration dec
est parfaitement légale en C89, aucune extension n'est nécessaire.Pour C89, vous devez déclarer toutes vos variables au début d'un bloc de portée .
Ainsi, votre
char c
déclaration est valide car elle se trouve en haut du bloc de portée de la boucle for. Mais, lachar *s
déclaration devrait être une erreur.la source
gcc
. Autrement dit, ne croyez pas qu'un programme peut être compilé pour signifier qu'il est conforme.Le regroupement des déclarations de variables en haut du bloc est un héritage probablement dû aux limitations des anciens compilateurs C primitifs. Tous les langages modernes recommandent et parfois même appliquent la déclaration des variables locales au dernier point: là où elles sont initialisées pour la première fois. Parce que cela élimine 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" (ou "final") quand vous le pouvez.
C ++ continue malheureusement d'accepter l'ancienne méthode de déclaration la plus élevée pour la compatibilité descendante avec C (un glisser de compatibilité C hors de beaucoup d'autres ...) Mais C ++ essaie de s'en éloigner:
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 divisé.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
la source
int y; ... if (x) { printf("X was true"); y=23;} return y;
...null
, all-bits-zero est souvent une valeur d' interruption utile. De plus, dans les langages qui spécifient explicitement que les variables par défaut à tous les bits-zéro, se fier à cette valeur n'est pas une erreur . Les compilateurs n'ont pas encore tendance à devenir trop farfelus avec leurs "optimisations", mais les rédacteurs de compilateurs continuent d'essayer d'être de plus en plus intelligents. Une option du compilateur pour initialiser des variables avec des variables pseudo-aléatoires délibérées peut être utile pour identifier les défauts, mais le simple fait de laisser le stockage contenant sa dernière valeur peut parfois masquer des défauts.D'un point de vue maintenabilité, plutôt que syntaxique, il y a au moins trois courants de pensée:
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 en un coup d'œil.
Déclarez toutes les variables aussi près que possible de l'endroit où elles ont été utilisées pour la première fois, afin que vous sachiez pourquoi chacune est nécessaire.
Déclarez toutes les variables au début du bloc de portée le plus interne, afin qu'elles soient hors de portée dès que possible et permettent au compilateur d'optimiser la mémoire et de vous dire si vous les utilisez accidentellement là où vous ne l'aviez pas prévu.
Je préfère généralement la première option, car je trouve que les autres me forcent souvent à parcourir le code pour les déclarations. La définition initiale de toutes les variables facilite également leur initialisation et leur visualisation à 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, dont j'en ai très peu. Un exemple pourrait être après a
fork()
, pour déclarer les variables nécessaires uniquement par le processus enfant. Pour moi, cet indicateur visuel est un rappel utile de leur objectif.la source
Comme d'autres l'ont noté, GCC est permissif à cet égard (et peut-être à d'autres compilateurs, selon les arguments avec lesquels ils sont appelés) même en mode 'C89', à moins que vous n'utilisiez la vérification 'pédantique'. Pour être honnête, il n'y a pas beaucoup de bonnes raisons de ne pas avoir de pédantisme; le code moderne de qualité doit toujours compiler sans avertissements (ou très peu où vous savez que vous faites quelque chose de spécifique qui est suspect pour le compilateur comme une erreur possible), donc si vous ne pouvez pas faire compiler votre code avec une configuration pédante, il a probablement besoin 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), en particulier la déclaration et l'initialisation simultanées d'une variable de contrôle de boucle dans les boucles `` for ''.
la source
Comme on l’a noté, il existe deux écoles de pensée à ce sujet.
1) Déclarez tout en haut des fonctions car l'année est 1987.
2) Déclarez le plus proche de la première utilisation et dans la plus petite portée possible.
Ma réponse à cela est FAITES LES DEUX! Laisse-moi expliquer:
Pour les fonctions longues, 1) rend la refactorisation très difficile. Si vous travaillez dans une base de code où les développeurs sont contre l'idée de sous-routines, alors vous aurez 50 déclarations de variables au début de la fonction et certaines d'entre elles pourraient simplement être un "i" pour une boucle for qui est au plus bas de la fonction.
J'ai donc développé la déclaration au sommet du SSPT à partir de cela et j'ai essayé de faire l'option 2) religieusement.
Je suis revenu à la première option à cause d'une chose: des fonctions courtes. Si vos fonctions sont assez courtes, alors vous aurez peu de variables locales et comme la fonction est courte, si vous les mettez en haut de la fonction, elles seront toujours proches de la première utilisation.
De plus, l'anti-modèle de «déclarer et définir à NULL» lorsque vous voulez déclarer en haut mais que vous n'avez pas fait certains calculs nécessaires à l'initialisation est résolu car les choses que vous devez initialiser seront probablement reçues comme arguments.
Alors maintenant, je pense que vous devez déclarer en haut des fonctions et aussi près que possible de la première utilisation. Alors les deux! Et la façon de le faire est d'utiliser des sous-programmes bien divisés.
Mais si vous travaillez sur une fonction longue, mettez les choses les plus proches de la première utilisation, car de cette façon, il sera plus facile d'extraire des méthodes.
Ma recette est la suivante. Pour toutes les variables locales, prenez la variable et déplacez sa déclaration vers le bas, compilez, puis déplacez la déclaration juste avant l'erreur de compilation. C'est la première utilisation. Faites ceci pour toutes les variables locales.
Maintenant, définissez un bloc de portée qui commence avant la déclaration et déplacez la fin jusqu'à ce que le programme se compile
Cela ne compile pas car il y a plus de code qui utilise foo. Nous pouvons remarquer que le compilateur a pu parcourir 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 "}" vers le bas jusqu'à ce qu'il se compile, et l'autre choix est d'inspecter le code et de déterminer si l'ordre peut être changé en:
Si l'ordre peut être changé, c'est probablement ce que vous voulez, car cela raccourcit la durée de vie des valeurs temporaires.
Une autre chose à noter, est-ce que la valeur de foo doit être préservée entre les blocs de code qui l'utilisent, ou pourrait-il simplement être un foo différent dans les deux. Par exemple
Ces situations nécessitent plus que ma procédure. Le développeur devra analyser le code pour déterminer ce qu'il doit faire.
Mais la première étape consiste à trouver la première utilisation. 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 se compile. Le compilateur identifiera ensuite d'autres utilisations. Essayez de créer un bloc de portée qui englobe les deux utilisations.
Une fois cette partie mécanique terminée, il devient plus facile d'analyser où se trouvent les données. Si une variable est utilisée dans un gros bloc de portée, analysez la situation et voyez si vous utilisez simplement la même variable pour deux choses différentes (comme un "i" qui s'utilise pour deux boucles for). Si les utilisations ne sont pas liées, créez de nouvelles variables pour chacune de ces utilisations non liées.
la source
Vous devez déclarer toutes les variables en haut ou "localement" dans la fonction. La réponse est:
Cela dépend du type de système que vous utilisez:
1 / Système embarqué (notamment lié à des vies comme Avion ou Voiture): Il permet d'utiliser la mémoire dynamique (ex: calloc, malloc, new ...). Imaginez que vous travaillez dans un très gros projet, avec 1000 ingénieurs. Et s'ils allouent une nouvelle mémoire dynamique et oublient de la supprimer (quand elle ne l'utilise plus)? Si le système embarqué fonctionne pendant une longue période, cela entraînera un débordement de pile et le logiciel sera corrompu. Pas facile de s'assurer de la qualité (le meilleur moyen est d'interdire la mémoire dynamique).
Si un avion fonctionne dans les 30 jours et ne s'arrête pas, que se passe-t-il si le logiciel est corrompu (lorsque l'avion est toujours en l'air)?
2 / Les autres systèmes comme le web, le PC (ont un grand espace mémoire):
Vous devez déclarer la variable "localement" pour optimiser l'utilisation de la mémoire. Si ces systèmes fonctionnent pendant une longue période et qu'un débordement de pile se produit (parce que quelqu'un a oublié de supprimer la mémoire dynamique). Faites simplement la chose simple pour réinitialiser le PC: P Cela n'a aucun impact sur la vie
la source
malloc()
. Bien que je n'ai jamais vu un appareil qui en est incapable, il est préférable d'éviter l'allocation dynamique sur les systèmes embarqués ( voir ici ). Mais cela n'a rien à voir avec l'endroit où vous déclarez vos variables dans une fonction.sub rsp, 1008
alloue de l'espace pour tout le tableau en dehors de l'instruction if. Cela est vrai pourclang
etgcc
à chaque version et niveau d'optimisation que j'ai essayé.Je vais citer quelques déclarations du manuel de gcc version 4.7.0 pour une explication claire.
"Le compilateur peut accepter plusieurs standards de base, tels que 'c90' ou 'c ++ 98', et les dialectes GNU de ces standards, tels que 'gnu90' ou 'gnu ++ 98'. En spécifiant un standard de base, le compilateur acceptera tous les programmes suivant 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 d'autres extensions GNU qui n'ont pas de signification dans ISO C90, comme l'omission du terme intermédiaire d'une expression?:. "
Je pense que le point clé de votre question est de savoir pourquoi gcc n'est-il pas conforme à C89 même si l'option "-std = c89" est utilisée. 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 dit que l'option "-std = c89" signifie simplement que les extensions qui contredisent C89 sont désactivées. Donc, cela n'a rien à voir avec certaines extensions qui n'ont pas de signification 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 qu'il devrait être totalement conforme à C89 à la première vue de l'option "-std = c89". Mais ce n'est pas le cas. Quant au problème de déclarer toutes les variables au début, c'est mieux ou pire, c'est juste une question d'habitude.
la source