web-dev-qa-db-fra.com

Où pouvez-vous et ne pouvez pas déclarer de nouvelles variables en C?

J'ai entendu (probablement par un enseignant) qu'il fallait déclarer toutes les variables en haut du programme/de la fonction et que le fait d'en déclarer de nouvelles parmi les énoncés pourrait poser problème.

Mais alors je lisais K & R et je suis tombé sur cette phrase: "Les déclarations de variables (y compris les initialisations) peuvent suivre l'accolade de gauche qui introduit toute instruction composée, pas seulement celle qui commence une fonction". Il suit avec un exemple: 

if (n > 0){
    int i;
    for (i=0;i<n;i++)
    ...
}

J'ai un peu joué avec le concept, et cela fonctionne même avec des tableaux. Par exemple: 

int main(){
    int x = 0 ;

    while (x<10){
        if (x>5){
            int y[x];
            y[0] = 10;
            printf("%d %d\n",y[0],y[4]);
        }
        x++;
    }
}

Alors, quand exactement je ne suis pas autorisé à déclarer des variables? Par exemple, que se passe-t-il si ma déclaration de variable ne se trouve pas juste après l'accolade d'ouverture? Comme ici:

int main(){
    int x = 10;

    x++;
    printf("%d\n",x);

    int z = 6;
    printf("%d\n",z);
}

Cela pourrait-il causer des problèmes en fonction du programme/de la machine? 

68
Daniel Scocco

J'entends aussi souvent dire que placer les variables au sommet de la fonction est la meilleure façon de faire les choses, mais je suis tout à fait en désaccord. Je préfère limiter les variables à la plus petite portée possible afin qu'elles aient moins de chance d'être mal utilisées. J'ai donc moins de choses à remplir mon espace mental dans chaque ligne du programme.

Bien que toutes les versions de C autorisent la portée du bloc lexical, vous pouvez déclarer les variables en fonction de la version de la norme C que vous ciblez:

C99 ou C++

Les compilateurs C modernes tels que gcc et clang prennent en charge les normes C99 et C11 , qui vous permettent de déclarer une variable n'importe où une instruction pourrait aller. La portée de la variable commence du point de la déclaration à la fin du bloc (accolade de fermeture suivante).

if( x < 10 ){
   printf("%d", 17);  // z is not in scope in this line
   int z = 42;
   printf("%d", z);   // z is in scope in this line
}

Vous pouvez également déclarer des variables à l'intérieur des initialiseurs de boucle. La variable n'existera que dans la boucle.

for(int i=0; i<10; i++){
    printf("%d", i);
}

ANSI C (C90)

Si vous ciblez l'ancien ANSI C standard, vous êtes limité à la déclaration de variables immédiatement après une accolade d'ouverture.1.

Cela ne signifie pas pour autant que vous deviez déclarer toutes vos variables au sommet de vos fonctions. En C, vous pouvez placer un bloc délimité par des accolades partout où une instruction peut aller (pas seulement après des choses comme if ou for) et vous pouvez l'utiliser pour introduire de nouvelles portées variables. Voici la version ANSI C des exemples précédents de C99:

if( x < 10 ){
   printf("%d", 17);  // z is not in scope in this line

   {
       int z = 42;
       printf("%d", z);   // z is in scope in this line
   }
}

{int i; for(i=0; i<10; i++){
    printf("%d", i);
}}

1 Notez que si vous utilisez gcc, vous devez passer l’indicateur --pedantic pour le forcer à appliquer la norme C90 et vous plaindre du fait que les variables sont déclarées au mauvais endroit. Si vous utilisez uniquement -std=c90, gcc accepte un sur-ensemble de C90 qui autorise également les déclarations de variable C99 plus souples.

106
hugomg

missingno couvre ce que permet ANSI C, mais il ne dit pas pourquoi vos professeurs vous ont dit de déclarer vos variables au sommet de vos fonctions. Déclarer des variables dans des endroits inhabituels peut rendre votre code plus difficile à lire, ce qui peut entraîner des erreurs.

Prenez le code suivant comme exemple.

#include <stdio.h>

int main() {
    int i, j;
    i = 20;
    j = 30;

    printf("(1) i: %d, j: %d\n", i, j);

    {
        int i;
        i = 88;
        j = 99;
        printf("(2) i: %d, j: %d\n", i, j);
    }

    printf("(3) i: %d, j: %d\n", i, j);

    return 0;
}

Comme vous pouvez le constater, j'ai déclaré i deux fois. Eh bien, pour être plus précis, j'ai déclaré deux variables, les deux avec le nom i. Vous pensez peut-être que cela provoquerait une erreur, mais ce n'est pas le cas, car les deux variables i se trouvent dans des portées différentes. Vous pouvez le voir plus clairement lorsque vous regardez le résultat de cette fonction.

(1) i: 20, j: 30
(2) i: 88, j: 99
(3) i: 20, j: 99

Premièrement, nous assignons 20 et 30 à i et j respectivement. Ensuite, entre les accolades, nous assignons 88 et 99. Alors, pourquoi alors la j garde-t-elle sa valeur, mais i revient-elle à 20 ans? C'est à cause des deux variables i différentes.

Entre les accolades intérieures, la variable i avec la valeur 20 est cachée et inaccessible, mais comme nous n’avons pas déclaré de nouvelle j, nous utilisons toujours la j à partir de la portée externe. Lorsque nous quittons l'ensemble des accolades intérieures, la i contenant la valeur 88 disparaît et nous avons à nouveau accès à la i avec la valeur 20.

Parfois, ce comportement est une bonne chose, d'autres fois, peut-être pas, mais il devrait être clair que si vous utilisez cette fonctionnalité de C sans discernement, vous pouvez vraiment rendre votre code confus et difficile à comprendre.

3
haydenmuhl

Si votre compilateur le permet, vous pourrez alors le déclarer n'importe où. En fait, le code est plus lisible (IMHO) lorsque vous déclarez la variable là où vous utilisez au lieu d’être au sommet d’une fonction, car cela facilite la détection des erreurs, par exemple. oublier d'initialiser la variable ou masquer accidentellement la variable.

1
Anders

Un article montre le code suivant:

//C99
printf("%d", 17);
int z=42;
printf("%d", z);

//ANSI C
printf("%d", 17);
{
    int z=42;
    printf("%d", z);
}

et je pense que l'implication est que ceux-ci sont équivalents. Ils ne sont pas. Si int z est placé au bas de cet extrait de code, cela provoque une erreur de redéfinition par rapport à la première définition z mais pas à la seconde.

Cependant, plusieurs lignes de:

//C99
for(int i=0; i<10; i++){}

ça marche. Afficher la subtilité de cette règle C99.

Personnellement, j'évite passionnément cette fonctionnalité C99.

L'argument selon lequel cela réduit la portée d'une variable est faux, comme le montrent ces exemples. En vertu de la nouvelle règle, vous ne pouvez pas déclarer une variable en toute sécurité tant que vous n’avez pas numérisé le bloc en entier. Auparavant, il vous suffisait de comprendre ce qui se passait à la tête de chaque bloc.

1
user2073625

Selon le langage de programmation The C de K & R - 

En C, toutes les variables doivent être déclarées avant leur utilisation, généralement au début de la fonction, avant toute instruction exécutable.

Ici vous pouvez voir Word habituellement ce n’est pas un must ..

0
Gagandeep kaur

En interne, toutes les variables locales à une fonction sont allouées sur une pile ou à l'intérieur de registres de la CPU, puis le code machine généré bascule entre les registres et la pile (appelé registre spill), si le compilateur est incorrect ou si la CPU n'a pas assez de registres pour garder toutes les balles jongler en l'air.

Pour allouer des éléments sur la pile, la CPU dispose de deux registres spéciaux, l'un appelé Stack Pointer (SP) et l'autre - Base Pointer (BP) ou pointeur de cadre (signifiant que le cadre de la pile est local à la portée de la fonction actuelle). SP pointe à l'intérieur de l'emplacement actuel sur une pile, tandis que BP pointe sur le jeu de données de travail (au-dessus de celui-ci) et les arguments de la fonction (au-dessous de celui-ci). Lorsque la fonction est appelée, elle enfonce le partenaire de la fonction appelant/parent dans la pile (indiquée par le SP), définit le SP actuel comme nouveau partenaire, puis augmente SP de le nombre d'octets générés par les registres sur la pile, le calcul est effectué et, en retour, il restaure le tiers de son parent en le supprimant de la pile.

En général, conserver vos variables dans leur propre domaine {}- pourrait accélérer la compilation et améliorer le code généré en réduisant la taille du graphique que le compilateur doit parcourir pour déterminer quelles variables sont utilisées où et comment. Dans certains cas (en particulier lorsque goto est impliqué), le compilateur peut rater le fait que la variable ne sera plus utilisée, à moins d'indication explicite de la portée d'utilisation du compilateur. Les compilateurs pourraient avoir une limite de temps/profondeur pour rechercher le graphe de programme.

Le compilateur peut placer les variables déclarées les unes à côté des autres dans la même zone de pile, ce qui signifie que le chargement d’une des variables préchargera toutes les autres dans le cache. De la même manière, la déclaration de la variable register pourrait donner au compilateur une indication selon laquelle vous souhaitez éviter à tout prix que cette variable ne soit pas déversée dans la pile.

Le strict standard C99 requiert des déclarations { explicites avant les déclarations, tandis que les extensions introduites par C++ et GCC autorisent la déclaration de vars plus loin dans le corps, ce qui complique les instructions goto et case. C++ permet en outre de déclarer des informations à l'intérieur de l'initialisation de la boucle, qui est limitée à la portée de la boucle.

Enfin et surtout, pour un autre être humain lisant votre code, il serait décourageant de voir le sommet d'une fonction encombrée d'une demi-centaine de déclarations de variables, au lieu d'être localisées à leurs emplacements d'utilisation. Il est également plus facile de commenter leur utilisation.

TLDR: utiliser {} pour déclarer explicitement la portée des variables peut aider à la fois le compilateur et le lecteur humain.

0
SmugLispWeenie

Avec clang et gcc, j'ai rencontré des problèmes majeurs avec le fichier . Gcc version 8.2.1 suivant 

  {
    char f1[]="This_is_part1 This_is_part2";
    char f2[64]; char f3[64];
    sscanf(f1,"%s %s",f2,f3);      //split part1 to f2, part2 to f3 
  }

ni le compilateur n'a aimé f1, f2 ou f3, pour être dans le bloc. Je devais déplacer f1, f2, f3 dans la zone de définition de fonction. le compilateur ne s’inquiétait pas de la définition d’un entier avec le bloc.

0
Leslie Satenstein