web-dev-qa-db-fra.com

Quand est-il possible d'utiliser une variable globale en C?

Apparemment, il existe une grande variété d'opinions, allant de " Jamais! Toujours encapsuler (même si c'est avec une simple macro!) " à " Ce n'est pas grave, utilisez-les quand c'est plus pratique que ne pas. "

Alors.

Des raisons spécifiques et concrètes (de préférence avec un exemple)

  • Pourquoi les variables globales sont dangereuses
  • Lorsque les variables globales devraient être utilisées à la place d'alternatives
  • Quelles alternatives existent pour ceux qui sont tentés d'utiliser des variables globales de manière inappropriée

Bien que cela soit subjectif, je choisirai une réponse (celle qui représente le mieux la relation amour/haine que chaque développeur devrait avoir avec les globals) et la communauté votera pour la leur juste en dessous.

Je pense qu'il est important que les débutants aient ce type de référence, mais s'il vous plaît, ne l'encombrez pas s'il existe une autre réponse très similaire au vôtre - ajoutez un commentaire ou modifiez la réponse de quelqu'un d'autre.

-Adam

43
Adam Davis

Les variables doivent toujours avoir la plus petite portée possible. L'argument derrière cela est que chaque fois que vous augmentez la portée, vous avez plus de code qui modifie potentiellement la variable, ainsi plus de complexité est induite dans la solution. 

Il est donc clair qu’il est préférable d’éviter d’utiliser des variables globales si la conception et la mise en œuvre le permettent naturellement. Pour cette raison, je préfère ne pas utiliser de variables globales sauf si elles sont vraiment nécessaires.

Je ne peux pas non plus être d'accord avec l'énoncé "jamais". Comme tout autre concept, les variables globales sont un outil à utiliser en cas de besoin. Je préférerais utiliser des variables globales plutôt que des constructions artificielles (telles que le transfert de pointeurs) qui ne masqueraient que l'intention réelle . Les bons exemples d'utilisation de variables globales sont les implémentations de modèle singleton ou l'accès aux registres dans des systèmes embarqués.

Comment détecter réellement les utilisations excessives de variables globales: inspection, inspection, inspection. Chaque fois que je vois une variable globale, je dois me demander: est-ce vraiment nécessaire à l'échelle mondiale?

42

La seule façon de faire fonctionner les variables globales est de leur donner des noms qui garantissent qu’elles sont uniques.

Ce nom a généralement un préfixe associé à un certain "module" ou une collection de fonctions pour lesquelles la variable globale est particulièrement ciblée ou significative.

Cela signifie que la variable "appartient" à ces fonctions - cela en fait partie. En effet, le global peut généralement être "encapsulé" avec une petite fonction qui va de pair avec les autres fonctions - dans le même préfixe du même nom de fichier .h.

Prime.

Lorsque vous faites cela, tout à coup, ce n'est plus vraiment global. Cela fait maintenant partie d'un module de fonctions connexes.

Cela peut toujours être fait. Avec un peu de réflexion, chaque variable précédemment globale peut être affectée à un ensemble de fonctions, affectée à un fichier .h spécifique et isolée avec des fonctions vous permettant de modifier la variable sans rien perdre.

Plutôt que de dire "n'utilisez jamais de variables globales", vous pouvez dire "attribuer les responsabilités de la variable globale à un module qui en a le plus de sens". 

15
S.Lott

Considérez ce koan: "si la portée est suffisamment étroite, tout est global".

À cet âge, il est toujours possible d’avoir besoin d’écrire un programme utilitaire très rapide pour effectuer un travail ponctuel.

Dans de tels cas, l'énergie nécessaire pour créer un accès sécurisé aux variables est supérieure à l'énergie économisée par les problèmes de débogage dans un utilitaire aussi petit.

C’est le seul cas auquel je puisse penser immédiatement où les variables globales sont judicieuses et relativement rare. Des programmes nouveaux et utiles, si petits qu'ils peuvent être complètement conservés dans la mémoire à court terme du cerveau, sont de moins en moins fréquents, mais ils existent toujours.

En fait, je pourrais affirmer hardiment que si le programme n’est pas si petit, les variables globales devraient être illégales.

  • Si la variable ne changera jamais, alors c'est une constante, pas une variable.
  • Si la variable nécessite un accès universel, il doit exister deux sous-routines pour l’obtenir et la définir, et elles doivent être synchronisées.
  • Si le programme commence petit et qu'il est peut-être plus volumineux plus tard, codez comme si le programme était volumineux aujourd'hui et supprimez les variables globales. Tous les programmes ne vont pas grandir! (Bien sûr, cela suppose que le programmeur est disposé à jeter du code de temps en temps.)
11
Paul Brinkley

Les variables globales en C sont utiles pour rendre le code plus lisible si une variable est requise par plusieurs méthodes (au lieu de la transmettre à chaque méthode). Cependant, ils sont dangereux car tous les emplacements ont la possibilité de modifier cette variable, ce qui rend potentiellement difficile la traçabilité des bogues. Si vous devez utiliser une variable globale, assurez-vous toujours qu'elle est uniquement modifiée directement par une méthode et que tous les autres appelants utilisent cette méthode. Cela facilitera beaucoup le débogage des problèmes liés aux modifications de cette variable.

8
Jeff Yates

Lorsque le code thread-safe ne vous inquiète pas : utilisez-les partout où cela fait sens, c'est-à-dire qu'il est logique d'exprimer quelque chose en tant qu'état global.

Lorsque votre code est multi-threadé : à éviter à tout prix. Les variables globales abstraites dans des files d'attente de travail ou dans une autre structure thread-safe, ou si c'est absolument nécessaire, mettez-les dans des verrous, en gardant à l'esprit qu'il s'agit probablement de goulots d'étranglement dans le programme.

7
Dan Lenski

Je venais du camp "jamais", jusqu'à ce que je commence à travailler dans l'industrie de la défense. Certaines normes de l'industrie exigent que les logiciels utilisent des variables globales au lieu d'une mémoire dynamique (malloc dans le cas C). Je dois repenser mon approche en matière d'allocation de mémoire dynamique pour certains des projets sur lesquels je travaille. Si vous pouvez protéger la mémoire "globale" avec les sémaphores, threads, etc. appropriés, cette approche peut être acceptable pour la gestion de votre mémoire.

4
gthuffman

La complexité du code n'est pas la seule optimisation du problème. Pour de nombreuses applications, l’optimisation des performances a une priorité bien plus grande. Mais plus important encore, l’utilisation de variables globales peut considérablement réduire la complexité du code dans de nombreuses situations. Il existe de nombreuses situations, peut-être spécialisées, dans lesquelles les variables globales sont non seulement une solution acceptable, mais également une solution préférée. Mon exemple spécialisé préféré est leur utilisation pour assurer la communication entre le thread principal d'une application avec une fonction de rappel audio s'exécutant dans un thread en temps réel.

Il est trompeur de suggérer que les variables globales constituent un passif dans les applications multithreads, car TOUTES les variables, quelle que soit leur portée, constituent un passif potentiel si elles sont exposées à des modifications sur plusieurs threads.

Utilisez les variables globales avec parcimonie. Les structures de données doivent être utilisées autant que possible pour organiser et isoler l'utilisation de l'espace de noms global.

La portée variable offre aux programmeurs une protection très utile - mais elle peut avoir un coût. Je suis arrivé à écrire sur les variables globales ce soir parce que je suis un programmeur expérimenté en Objective-C qui est souvent frustré par les barrières que l’objet d’objets orientées sur l’accès aux données. Je dirais que le fanatisme anti-global provient principalement de programmeurs plus jeunes, férus de théorie, expérimentés principalement avec des API orientées objet isolément, sans une expérience pratique approfondie des API système et de leur interaction dans le développement d'applications. Mais je dois admettre que je suis frustré lorsque les fournisseurs utilisent l'espace de noms de manière négligente. Plusieurs distributions Linux avaient par exemple "PI" et "TWOPI" globalement prédéfinies, ce qui cassait beaucoup de mon code personnel. 

3
ctpenrose

Vous devez également déterminer dans quel contexte la variable globale sera utilisée. A l'avenir, voulez-vous que ce code soit dupliqué?.

Par exemple, si vous utilisez un socket au sein du système pour accéder à une ressource. À l'avenir, voudrez-vous accéder à plus d'une de ces ressources, si la réponse est oui, je resterais à l'écart des globals afin qu'un refactor majeur ne soit pas nécessaire.

2
JProgrammer

C’est un outil comme un autre, habituellement surutilisé, mais je ne pense pas qu’ils soient diaboliques.

Par exemple, j'ai un programme qui agit vraiment comme une base de données en ligne. Les données sont stockées dans la mémoire mais d’autres programmes peuvent les manipuler. Il existe des routines internes qui ressemblent beaucoup aux procédures stockées et aux déclencheurs d'une base de données.

Ce programme a des centaines de variables globales, mais si vous y réfléchissez, il s’agit d’une base de données mais d’un très grand nombre de variables globales. 

Ce programme est utilisé depuis une dizaine d’années depuis plusieurs versions et n’a jamais posé de problème. Je le referais dans une minute.

J'admets que dans ce cas, les vars globaux sont des objets qui ont des méthodes utilisées pour changer l'état de l'objet. Donc, retrouver qui a changé l'objet pendant le débogage n'est pas un problème, car je peux toujours définir un point d'arrêt sur la routine qui change l'état de l'objet. Ou encore plus simple, je viens d'activer la journalisation intégrée qui enregistre les modifications.

1
Kevin Gale
  • Quand ne pas utiliser: Les variables globales sont dangereuses car la seule façon de savoir comment la variable globale a été modifiée est de suivre l'intégralité du code source dans le fichier .c dans lequel elles sont déclarées (ou tous les fichiers .c si c'est externe aussi). Si votre code devient bogué, vous devez rechercher dans votre fichier source entier pour voir quelles fonctions le modifient et quand. C'est un cauchemar de déboguer quand ça ne va pas. Nous prenons souvent pour acquis l'ingéniosité qui sous-tend le concept de variables locales hors de portée - il est facile de retracer
  • Quand utiliser: Les variables globales doivent être utilisées lorsque son utilisation n'est pas excessivement masquée et que le coût d'utilisation des variables locales est excessivement complexe au point de compromettre sa lisibilité. J'entends par là le besoin d'avoir à ajouter un paramètre supplémentaire pour pouvoir utiliser des arguments, des retours et des pointeurs, entre autres choses. Trois exemples classiques: Lorsque j'utilise les piles pop et push, elles sont partagées entre des fonctions. Bien sûr, je pourrais utiliser des variables locales, mais je devrais alors passer des pointeurs comme paramètre supplémentaire. Le deuxième exemple classique se trouve dans "Le langage de programmation C" de K & R, qui définit les fonctions getch () et ungetch () qui partagent un tableau de mémoire tampon de caractères global. Encore une fois, nous n'avons pas besoin de le rendre global, mais la complexité ajoutée en vaut-elle la peine quand il est assez difficile de gâcher l'utilisation du tampon? Le troisième exemple est quelque chose que vous trouverez dans l’espace inclus parmi les amateurs d’Arduino. Beaucoup de fonctions de la fonction principale loop partagent la même fonction millis (), qui correspond au moment instantané du moment où la fonction est appelée. Parce que la vitesse d'horloge n'est pas infinie, le millis () sera differ [ au sein d'une seule boucle. Pour la rendre constante, prenez un instantané du temps précédent à chaque boucle et enregistrez-le dans une variable globale. L’instantané horaire sera désormais le même que celui auquel accèdent les nombreuses fonctions.
  • Alternatives: Pas grand chose. Restez fidèle à la portée locale, surtout au début du projet, plutôt que l'inverse. Au fur et à mesure que le projet se développe et si vous pensez que la complexité peut être réduite à l'aide de variables globales, faites-le ainsi, mais uniquement si cela répond aux exigences du point deux. Et rappelez-vous, utiliser une portée locale et avoir un code plus compliqué est le moindre mal comparé à l'utilisation irresponsable de variables globales.
1
Dean P

Les variables globales doivent être utilisées lorsque plusieurs fonctions doivent accéder aux données ou écrire dans un objet. Par exemple, si vous deviez transmettre des données ou une référence à plusieurs fonctions, telles qu'un seul fichier journal, un pool de connexions ou une référence matérielle à laquelle vous devez accéder via l'application. Cela évite les déclarations de fonction très longues et les allocations importantes de données dupliquées. 

Vous devez généralement non utiliser des variables globales, sauf en cas de nécessité absolue, car elles ne sont nettoyées que lorsque le programme vous le demande explicitement ou que votre programme se termine. Si vous exécutez une application multithread, plusieurs fonctions peuvent écrire dans la variable en même temps. Si vous avez un bogue, il peut être plus difficile de le localiser, car vous ne savez pas quelle fonction modifie la variable. Vous rencontrez également le problème des conflits de noms sauf si vous utilisez une convention de noms qui attribue explicitement un nom unique aux variables globales.

1
Steropes

Lorsque vous déclarez des constantes.

1
user15039

Je peux penser à plusieurs raisons:

débogage/tests (avertissement - ce code n'a pas été testé):

#include <stdio.h>
#define MAX_INPUT 46
int runs=0;
int fib1(int n){
    ++runs;
    return n>2?fib1(n-1)+fib1(n-2):1;
};
int fib2(int n,int *cache,int *len){
    ++runs;
    if(n<=2){
        if(*len==2)
            return 1;
        *len=2;
        return cache[0]=cache[1]=1;
    }else if(*len>=n)
        return cache[n-1];
    else{
        if(*len!=n-1)
            fib2(n-1,cache,len);
        *len=n;
        return cache[n-1]=cache[n-2]+cache[n-3];
    };
};
int main(){
    int n;
    int cache[MAX_INPUT];
    int len=0;
    scanf("%i",&n);
    if(!n||n>MAX_INPUT)
        return 0;
    printf("fib1(%i)==%i",n,fib1(n));
    printf(", %i run(s)\n",runs);
    runs=0;
    printf("fib2(%i)==%i",n,fib2(n,&cache,&len));
    printf(", %i run(s)\n",runs);
    main();
};

J'ai utilisé des variables étendues pour fib2, mais c'est un scénario de plus où les globales pourraient être utiles (fonctions mathématiques pures qui nécessitent de stocker des données pour éviter de prendre pour toujours).

programmes utilisés une seule fois (par exemple pour un concours) ou lorsque le temps de développement doit être raccourci

les globales sont utiles en tant que constantes typées, où une fonction nécessite quelque part * int au lieu de int.

En général, j'évite les globals si j'ai l'intention d'utiliser le programme plus d'une journée.

1
yingted

Je suis dans le camp "jamais" ici; si vous avez besoin d'une variable globale, utilisez au moins un modèle singleton pattern . De cette façon, vous bénéficiez des avantages d'une instanciation paresseuse et vous n'encombrez pas l'espace de noms global.

0
Nik Reiman

Les constantes globales sont utiles - vous obtenez plus de sécurité de type que les macros de préprocesseur et il est tout aussi facile de modifier la valeur si vous le souhaitez.

Les variables globales ont certaines utilisations, par exemple si le fonctionnement de nombreuses parties d'un programme dépend d'un état particulier de la machine à états. Tant que vous limitez le nombre d'emplacements pouvant modifier la variable, il n'est pas si mal de traquer les bogues qui la concernent.

Les variables globales deviennent dangereuses presque dès que vous créez plusieurs threads. Dans ce cas, vous devriez vraiment limiter la portée à (tout au plus) une variable de fichier globale (en la déclarant statique) et des méthodes getter/setter qui le protègent des accès multiples là où cela pourrait être dangereux.

0
Adam Hawes