Menu

La portée en C : portée de bloc, de fonction et de fichier expliquées

Où une variable C est visible et combien de temps elle vit - portée de bloc, paramètres de fonction, globales à portée fichier, variables locales static qui survivent entre les appels, fonctions static, masquage, et pourquoi les globales posent problème.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Deux questions sur chaque variable

Toute variable en C a deux propriétés distinctes, et les confondre est la source de la plupart des surprises de cette page :

  • La portée - le nom peut être utilisé. Une question de compilation.
  • La durée de vie - combien de temps le stockage existe. Une question d'exécution.

Elles vont généralement de pair (une locale ordinaire est visible dans son bloc et vit exactement aussi longtemps), mais static les découple, ce qui en fait le mot-clé à comprendre ici.

La portée de bloc

Une variable déclarée entre accolades est visible de sa déclaration jusqu'à l'accolade fermante, et nulle part ailleurs.

Le bloc interne voit outer, car il y est imbriqué. Le bloc externe ne voit pas inner. Cela s'applique à chaque paire d'accolades - corps de fonction, corps de if, corps de boucle, ou un bloc nu comme celui ci-dessus.

Les boucles rendent cela concret. Un compteur déclaré dans un en-tête de for appartient à la boucle :

for (int i = 0; i < 5; i++) {
    /* i vit ici */
}
/* i n'existe pas ici */

C'est exactement ce que vous voulez : deux boucles dans la même fonction peuvent chacune utiliser i sans interférence, et le compteur ne peut pas être lu accidentellement après la boucle. Si vous avez besoin de la valeur ensuite - l'indice où une recherche s'est arrêtée - déclarez-la avant la boucle.

Les paramètres de fonction ont la portée du corps de la fonction, ils se comportent donc comme des locales déclarées en haut de celui-ci.

C99 autorise une déclaration n'importe où dans un bloc, pas seulement en haut. Déclarez les variables là où vous en avez besoin pour la première fois ; une variable à courte portée est une variable avec moins de façons d'être fausse.

La durée de vie : le stockage automatique

Une locale ordinaire a un stockage automatique : elle vient à l'existence quand le contrôle entre dans son bloc et est détruite quand il en sort. Sa mémoire vit sur la pile.

Cela affiche 1 trois fois. Chaque appel obtient un count tout neuf, initialisé à 0, et jeté au retour. C'est aussi pourquoi chaque trame d'une fonction récursive a sa propre copie de chaque variable locale.

Deux conséquences à énoncer clairement. Une locale non initialisée contient du garbage, pas zéro - les octets qui étaient sur la pile. Et renvoyer un pointeur vers une locale est un comportement indéfini, car le stockage a disparu à l'instant du retour :

/* BUG: buf n'existe plus apres le return */
char *broken(void) {
    char buf[64] = "hello";
    return buf;
}

Les locales static : de la mémoire entre les appels

Mettez static sur une locale et sa durée de vie devient celle de tout le programme, tandis que sa portée reste exactement la même.

Maintenant le compteur affiche 1, 2, 3. Le = 0 s'exécute une fois, avant que main ne démarre - pas à chaque appel.

Trois règles pour les locales static :

  • L'initialiseur doit être une expression constante, car il est appliqué au démarrage du programme, pas à l'exécution.
  • Sans initialiseur, une static est initialisée à zéro (contrairement à une locale automatique, qui contient du garbage).
  • La variable reste privée à la fonction. Aucun autre code ne peut la nommer.

Ce dernier point est tout l'attrait : une fonction peut se souvenir de quelque chose entre les appels sans exposer une globale que n'importe quoi pourrait modifier. Utilisez-la pour des compteurs d'appels, des tables de correspondance construites au premier usage, et des drapeaux d'initialisation unique.

Le coût est que la fonction n'est plus une correspondance pure entrée-sortie - les mêmes arguments peuvent donner des réponses différentes - ce qui la rend plus difficile à tester et dangereuse à appeler depuis plusieurs fils d'exécution à la fois sans protection. Utilisez les locales static délibérément, pas par commodité.

La portée fichier : les globales

Une variable déclarée hors de toute fonction a une portée fichier. Elle est visible de sa déclaration jusqu'à la fin du fichier, dans chaque fonction en dessous, et sa durée de vie est celle du programme entier.

Les globales sont initialisées à zéro par défaut, donc totalOperations démarre à 0 même sans le = 0.

Par défaut, une globale a aussi une liaison externe : d'autres fichiers .c du même programme peuvent l'atteindre en la déclarant extern :

/* dans stats.c */
int totalOperations = 0;          /* la definition - exactement une dans le programme */

/* dans main.c */
extern int totalOperations;       /* une declaration : "elle existe quelque part" */

Mettez la déclaration extern dans un fichier d'en-tête pour que chaque utilisateur voie la même. Notez l'asymétrie : la déclaration extern dit que la variable existe, et exactement un fichier .c doit réellement la définir.

Pourquoi les globales mordent

Les globales sont la façon la plus facile de partager des données et la façon la plus facile de créer des bugs que vous ne pouvez pas localiser. Quatre problèmes concrets :

N'importe quoi peut les changer. Quand totalOperations contient une mauvaise valeur, le coupable est n'importe quelle ligne de n'importe quel fichier. Avec un paramètre, les suspects sont les points d'appel que vous voyez.

Les fonctions deviennent intestables. Une fonction qui lit une globale ne peut pas être appelée isolément ; il faut d'abord préparer l'état global, et penser à le réinitialiser après.

Le nom est à l'échelle du programme. Une globale appelée count ou buffer finira par entrer en collision avec celle de quelqu'un d'autre.

Leurs initialiseurs doivent être des constantes de compilation. En C, une globale ne peut être initialisée qu'avec une expression constante - int limit = readConfig(); ne compile pas, et int b = a * 2; à portée fichier non plus. Tout ce qui est calculé doit être affecté à l'exécution par une fonction d'initialisation, et chaque fichier qui touche la globale avant cet appel voit un 0 silencieux.

Les alternatives sont presque toujours disponibles : passez les valeurs en paramètres, renvoyez les résultats, et regroupez l'état lié dans une structure passée explicitement. De véritables exceptions existent - un objet de configuration à l'échelle du programme, un descripteur de journalisation - et même celles-là sont meilleures en static à portée fichier avec des fonctions d'accès, ce qui est la section suivante.

static à portée fichier : la confidentialité

Sur une variable globale ou une fonction, static signifie tout autre chose que sur une locale : la liaison interne. Le nom devient privé à son propre fichier .c et ne peut être atteint depuis aucun autre.

/* counter.c */
#include "counter.h"

static int count = 0;           /* prive a ce fichier - aucun autre ne peut y toucher */

static void validate(void) {    /* un helper prive, hors de l'interface */
    if (count < 0) count = 0;
}

void increment(void) {          /* public : declare dans counter.h */
    count++;
    validate();
}

int getCount(void) {            /* public */
    return count;
}

Un autre fichier peut appeler increment et getCount, mais il ne peut pas voir count ni appeler validate - l'éditeur de liens ne résoudra pas ces noms. C'est le système de modules du C, tel qu'il est : un en-tête déclarant les fonctions publiques, et static sur tout le reste.

Deux bénéfices pratiques au-delà de la propreté. Deux fichiers peuvent chacun avoir un static void validate(void) sans collision, ce qu'ils ne pourraient pas si les fonctions étaient publiques. Et le compilateur sait qu'une fonction static n'a pas d'appelants hors du fichier, il peut donc l'inliner plus agressivement ou avertir qu'elle est inutilisée.

Le mot-clé a donc deux sens distincts, décidés par il apparaît :

static sur une variable LOCALE ->  la duree de vie devient permanente (portee inchangee)
static a portee FICHIER        ->  la liaison devient interne         (duree de vie inchangee)

Le masquage

Déclarer dans une portée interne un nom qui existe déjà dans une portée externe le masque : dans la portée interne, le nom désigne la nouvelle variable et l'externe est inatteignable.

C'est légal, et parfois intentionnel. Plus souvent c'est un accident, et un accident coûteux : une fonction censée mettre à jour une globale met à jour une locale du même nom, et la globale ne change jamais. La même chose arrive quand une locale masque un paramètre, et l'affectation que vous destiniez à l'entrée ne va nulle part.

gcc -Wshadow signale chaque cas. Ce n'est pas dans -Wall, alors activez-le explicitement :

gcc -Wall -Wextra -Wshadow program.c -o program

L'habitude qui évite entièrement le problème est de nommer par rôle plutôt que par type : totalScore et itemScore ne peuvent pas se masquer, alors que deux variables appelées n finiront par le faire.

Une référence rapide

declaree dans un bloc            portee de bloc, duree de vie automatique, garbage si non initialisee
parametre de fonction            portee du corps de la fonction, une copie de l'argument
static dans une fonction         portee de bloc, duree de vie du PROGRAMME, initialisee a zero, conservee entre appels
declaree hors de toute fonction  portee fichier, duree de vie du programme, initialisee a zero, visible des autres fichiers
static hors de toute fonction    portee fichier, duree de vie du programme, PRIVEE a ce fichier .c
declaration extern               nomme une variable definie dans un autre fichier

Questions fréquentes

Qu'est-ce que la portée en C ?

La région de code où un nom est visible. Une variable déclarée dans un bloc (une paire d'accolades) n'est visible que dans ce bloc - c'est la portée de bloc. Une variable déclarée hors de toute fonction a une portée fichier et est visible de sa déclaration jusqu'à la fin du fichier.

Que fait static sur une variable locale en C ?

Il change la durée de vie de la variable sans changer sa portée. Une locale static est créée une fois, initialisée une fois, et garde sa valeur entre les appels, mais elle reste visible uniquement dans sa fonction. C'est le moyen de donner de la mémoire à une fonction sans utiliser de globale.

Quelle est la différence entre static et global en C ?

Une globale simple est visible par tous les fichiers du programme, que les autres fichiers peuvent atteindre avec extern. Une variable ou une fonction static à portée fichier est privée à ce seul fichier .c - les autres fichiers ne peuvent pas s'y lier. static à portée fichier concerne le masquage ; static sur une locale concerne la durée de vie.

Pourquoi les variables globales sont-elles mauvaises en C ?

N'importe quelle fonction peut les changer, donc un bug peut venir de n'importe où dans le programme ; les fonctions qui les lisent ne peuvent pas être testées isolément ; et le nom occupe tout le programme, invitant les collisions. Passez les valeurs en paramètres et renvoyez des résultats, et là où un état partagé est réellement nécessaire, rendez-le static à portée fichier pour qu'un seul fichier puisse y toucher.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER