Concept du mot-clé statique du point de vue du C intégré

9
static volatile unsigned char   PORTB   @ 0x06;

Il s'agit d'une ligne de code dans un fichier d'en-tête de microcontrôleur PIC. L' @opérateur est utilisé pour stocker la valeur PORTB à l'intérieur de l'adresse 0x06, qui est un registre à l'intérieur du contrôleur PIC qui représente PORTB. Jusqu'à ce point, j'ai une idée claire.

Cette ligne est déclarée comme une variable globale dans un fichier d'en-tête ( .h). Donc, d'après ce que j'ai appris sur le langage C, une "variable globale statique" n'est visible par aucun autre fichier - ou, simplement, les variables / fonctions globales statiques ne peuvent pas être utilisées en dehors du fichier actuel.

Ensuite, comment ce mot clé peut-il PORTBêtre visible dans mon fichier source principal et dans de nombreux autres fichiers d'en-tête que j'ai créés manuellement?

Sur mon fichier source principal, j'ai seulement ajouté le fichier d'en #include pic.h- tête Cela a-t-il quelque chose à voir avec ma question?

Electro Voyager
la source
2
pas de problème avec la question mais mauvaise section SE j'ai peur
gommer
static est normalement utilisé dans une fonction pour spécifier que la variable est créée une fois et conserve sa valeur d'une exécution d'une fonction à l'autre. une variable globale est une variable créée en dehors de toute fonction afin qu'elle soit visible partout. global statique n'a pas vraiment de sens.
Finbarr
8
@Finbarr Wrong. staticles globaux sont visibles à l'intérieur de l'unité de compilation unique et ne sont pas exportés au-delà. Ils ressemblent beaucoup aux privatemembres d'une classe de POO. C'est-à-dire toutes les variables qui doivent être partagées entre différentes fonctions à l'intérieur d'une unité de compilation mais qui ne sont pas censées être visibles en dehors de ce que cu devrait être static. Cela réduit également le "clobbering" de l'espace de noms global du programme.
JimmyB
2
Re "L'opérateur @ est utilisé pour stocker la valeur PORTB à l'intérieur de l'adresse 0x06" . Vraiment? N'est-ce pas plus comme "L'opérateur @ est utilisé pour stocker la variable" PORTB "à l'adresse de mémoire absolue 0x06" ?
Peter Mortensen

Réponses:

20

Le mot-clé «statique» en C a deux significations fondamentalement différentes.

Limiter la portée

Dans ce contexte, «statique» s'associe à «extern» pour contrôler la portée d'un nom de variable ou de fonction. Statique fait que le nom de la variable ou de la fonction n'est disponible que dans une seule unité de compilation et uniquement disponible pour le code qui existe après la déclaration / définition dans le texte de l'unité de compilation.

Cette limitation elle-même ne signifie vraiment que si et seulement si vous avez plusieurs unités de compilation dans votre projet. Si vous n'avez qu'une seule unité de compilation, cela fait toujours des choses, mais ces effets sont pour la plupart inutiles (sauf si vous aimez creuser dans des fichiers objets pour lire ce que le compilateur a généré.)

Comme indiqué, ce mot-clé dans ce contexte s'apparie avec le mot-clé 'extern', qui fait le contraire - en rendant le nom de variable ou de fonction pouvant être lié avec le même nom que celui trouvé dans d'autres unités de compilation. Ainsi, vous pouvez considérer «statique» comme nécessitant que la variable ou le nom soit trouvé dans l'unité de compilation actuelle, tandis que «externe» permet le lien entre les unités de compilation croisée.

Durée de vie statique

La durée de vie statique signifie que la variable existe pendant toute la durée du programme (aussi longue soit-elle). Lorsque vous utilisez 'statique' pour déclarer / définir une variable dans une fonction, cela signifie que la variable est créée quelque temps avant sa première utilisation ( ce qui signifie, chaque fois que je l'ai vécu, que la variable est créée avant le début de main ()) et n'est pas détruite par la suite. Pas même lorsque l'exécution de la fonction est terminée et qu'elle revient à son appelant. Et tout comme les variables de durée de vie statiques déclarées en dehors des fonctions, elles sont initialisées au même moment - avant le début de main () - à un zéro sémantique (si aucune initialisation n'est fournie) ou à une valeur explicite spécifiée, si elle est donnée.

Ceci est différent des variables de fonction de type `` auto '', qui sont créées nouvelles (ou comme si elles étaient nouvelles) chaque fois que la fonction est entrée, puis sont détruites (ou comme si elles étaient détruites) lorsque la fonction se ferme.

Contrairement à l'impact de l'application de «statique» sur une définition de variable en dehors d'une fonction, qui a un impact direct sur sa portée, déclarer une variable de fonction (dans un corps de fonction, évidemment) comme «statique» n'a pas impact sur sa portée. La portée est déterminée par le fait qu'elle a été définie au sein d'un corps de fonction. Les variables de durée de vie statiques définies dans les fonctions ont la même étendue que les autres variables «auto» définies dans les corps de fonction - étendue de la fonction.

Sommaire

Ainsi, le mot clé «statique» a des contextes différents avec ce qui équivaut à «des significations très différentes». La raison pour laquelle il a été utilisé de deux manières, comme celle-ci, était d'éviter d'utiliser un autre mot clé. (Il y a eu une longue discussion à ce sujet.) Il a été estimé que les programmeurs pouvaient tolérer l'utilisation et la valeur d'éviter encore un autre mot-clé dans la langue était plus importante (que les arguments autrement).

(Toutes les variables déclarées en dehors des fonctions ont une durée de vie statique et n'ont pas besoin du mot clé 'statique' pour que cela soit vrai. Donc, ce type de mot clé libéré pour être utilisé ici signifie quelque chose de complètement différent: 'visible uniquement dans une seule compilation "C'est un hack, en quelque sorte.)

Note spécifique

caractère volatile non signé statique PORTB @ 0x06;

Le mot «statique» ici doit être interprété comme signifiant que l'éditeur de liens ne tentera pas de faire correspondre plusieurs occurrences de PORTB qui peuvent être trouvées dans plusieurs unités de compilation (en supposant que votre code en a plusieurs).

Il utilise une syntaxe spéciale (non portable) pour spécifier «l'emplacement» (ou la valeur numérique de l'étiquette qui est généralement une adresse) de PORTB. L'éditeur de liens reçoit donc l'adresse et n'a pas besoin d'en trouver une. Si vous aviez deux unités de compilation utilisant cette ligne, elles finiraient chacune par pointer vers le même endroit, de toute façon. Il n'est donc pas nécessaire de l'étiqueter «extern», ici.

S'ils avaient utilisé «extern», cela pourrait poser un problème. L'éditeur de liens pourrait alors voir (et tenter de faire correspondre) plusieurs références à PORTB trouvées dans plusieurs compilations. Si tous spécifient une adresse comme celle-ci, et que les adresses ne sont PAS les mêmes pour une raison quelconque [erreur?], Alors qu'est-ce que c'est censé faire? Se plaindre? Ou? (Techniquement, avec 'extern' la règle de base serait que seulement UN unité de compilation spécifierait la valeur et les autres ne devraient pas.)

Il est simplement plus facile de l'étiqueter comme `` statique '', en évitant de faire en sorte que l'éditeur de liens s'inquiète des conflits, et il suffit de blâmer toute erreur pour les adresses mal appariées sur quiconque a changé l'adresse en quelque chose qu'elle ne devrait pas être.

Dans les deux cas, la variable est traitée comme ayant une «durée de vie statique». (Et «volatile».)

Une déclaration n'est pas une définition , mais toutes les définitions sont des déclarations

En C, une définition crée un objet. Il le déclare également. Mais une déclaration ne crée généralement pas (voir la puce ci-dessous) un objet.

Voici les définitions et déclarations:

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

Les éléments suivants ne sont pas des définitions, mais uniquement des déclarations:

extern int b;
extern int f();

Notez que les déclarations ne créent pas d'objet réel. Ils déclarent uniquement les détails à ce sujet, que le compilateur peut ensuite utiliser pour aider à générer le code correct et à fournir des messages d'avertissement et d'erreur, le cas échéant.

  • Ci-dessus, je dis «habituellement» à bon escient. Dans certains cas, une déclaration peut créer un objet et est donc promue à une définition par l'éditeur de liens (jamais par le compilateur). Ainsi, même dans ce cas rare, le compilateur C pense toujours que la déclaration n'est qu'une déclaration. C'est la phase de l'éditeur de liens qui fait les promotions nécessaires d'une déclaration. Gardez cela à l'esprit.

    Dans les exemples ci-dessus, s'il s'avère qu'il n'y a que des déclarations pour un "extern int b;" dans toutes les unités de compilation liées, l'éditeur de liens est chargé de créer une définition. Sachez qu'il s'agit d'un événement de liaison. Le compilateur est complètement ignorant, lors de la compilation. Elle ne peut être déterminée au moment de la liaison que si une déclaration de ce type est la plus promue.

    Le compilateur sait que "static int a;" ne peut pas être promu par l'éditeur de liens au moment du lien, il s'agit donc en fait d'une définition au moment de la compilation .

jonk
la source
3
Grande réponse, +1! Un seul point mineur: ils auraient pu utiliser extern, et ce serait la façon la plus appropriée de le faire en C: déclarer la variable externdans un fichier d'en-tête à inclure plusieurs fois dans le programme et la définir dans un fichier non en-tête à compiler et lié exactement une fois. Après tout, PORTB est censé être exactement une instance de la variable à laquelle différents cu peuvent se référer. Donc, l'utilisation staticici est une sorte de raccourci qu'ils ont pris pour éviter d'avoir besoin d'un autre fichier .c en plus du fichier d'en-tête.
JimmyB
Je voudrais également noter qu'une variable statique déclarée dans une fonction n'est pas modifiée entre les appels de fonction, ce qui peut être utile pour les fonctions qui doivent conserver une sorte d'information d'état (je l'ai utilisée spécifiquement à cette fin dans le passé).
Peter Smith
@Peter, je pense avoir dit ça. Mais peut-être pas aussi bien que vous l'auriez souhaité?
jonk
@JimmyB Non, ils n'auraient pas pu utiliser «extern» à la place pour les déclarations de variables de fonction qui se comportent comme «statiques». 'extern' est déjà une option pour les déclarations de variables (pas les définitions) au sein des corps de fonction et sert un objectif différent - fournir un accès en temps de liaison aux variables définies en dehors de toute fonction. Mais il est possible que je comprenne mal votre point également.
jonk
1
@JimmyB La liaison externe serait certainement possible, même si je ne sais pas si c'est "plus approprié". Une considération est que le compilateur peut être en mesure d'émettre du code plus optimisé si les informations sont trouvées dans l'unité de traduction. Pour les scénarios intégrés, l'enregistrement de cycles sur chaque instruction d'E / S peut être un gros problème.
Cort Ammon
9

staticles s ne sont pas visibles en dehors de l' unité de compilation actuelle ou "unité de traduction". Ce n'est pas la même chose que le même fichier .

Notez que vous incluez le fichier d'en-tête dans n'importe quel fichier source où vous pouvez avoir besoin des variables déclarées dans l'en-tête. Cette inclusion fait du fichier d'en-tête une partie de l'unité de traduction actuelle et (une instance de) la variable visible à l'intérieur.

JimmyB
la source
Merci pour votre réponse. "Unité de compilation", Désolé je ne comprends pas, pouvez-vous expliquer ce terme. Permettez-moi de vous poser une autre question, même si nous voulons utiliser des variables et des fonctions écrites dans un autre fichier, nous devons d'abord INCLURE ce fichier dans notre FICHIER SOURCE principal. Alors pourquoi le mot-clé "statique volatile" dans ce fichier d'en-tête.
Electro Voyager
1
Discussion assez approfondie sur stackoverflow.com/questions/572547/what-does-static-mean-in-c
Peter Smith
3
@ElectroVoyager; si vous incluez le même en-tête contenant une déclaration statique dans plusieurs fichiers source c, alors chacun de ces fichiers aura une variable statique du même nom, mais ce ne sera pas la même variable .
Peter Smith
2
Depuis le lien @JimmyB: Files included by using the #include preprocessor directive become part of the compilation unit.lorsque vous incluez votre fichier d'en-tête (.h) dans un fichier .c, pensez-y comme l'insertion du contenu de l'en-tête dans le fichier source, et maintenant, c'est votre unité de compilation. Si vous déclarez cette variable statique ou cette fonction dans un fichier .c, vous ne pouvez les utiliser que dans le même fichier qui, à la fin, sera une autre unité de compilation.
gustavovelascoh
5

Je vais essayer de résumer les commentaires et la réponse de @ JimmyB avec un exemple explicatif:

Supposons que cet ensemble de fichiers:

static_test.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

static.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

Vous pouvez compiler et exécuter le code en utilisant gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_testpour utiliser l'en-tête statique ou gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testpour utiliser l'en-tête non statique.

Notez que deux unités de compilation sont présentes ici: static_src et static_test. Lorsque vous utilisez la version statique de l'en-tête ( -DUSE_STATIC=1), une version de varet say_hellosera disponible pour chaque unité de compilation, c'est-à-dire que les deux unités peuvent les utiliser, mais vérifiez que même si la var_add_one()fonction incrémente sa var variable, lorsque la fonction principale imprime sa var variable , c'est toujours 64:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64

Maintenant, si vous essayez de compiler et d'exécuter le code, en utilisant la version non statique ( -DUSE_STATIC=0), il générera une erreur de liaison en raison de la définition de variable dupliquée:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

J'espère que cela pourrait vous aider à clarifier cette question.

gustavovelascoh
la source
4

#include pic.hsignifie à peu près "copier le contenu de pic.h dans le fichier actuel". Par conséquent, chaque fichier qui inclut pic.hobtient sa propre définition locale de PORTB.

Vous vous demandez peut-être pourquoi il n’existe pas de définition globale unique de PORTB. La raison est assez simple: vous ne pouvez définir une variable globale que dans un seul fichier C, donc si vous souhaitez utiliser PORTBplusieurs fichiers dans votre projet, vous auriez besoin pic.hd'une déclaration de PORTBet pic.cavec sa définition . Laisser chaque fichier C définir sa propre copie de PORTBfacilite la construction de code, car vous n'avez pas à inclure dans vos fichiers de projet que vous n'avez pas écrits.

Un avantage supplémentaire des variables statiques par rapport aux globales est que vous obtenez moins de conflits de nommage. Le fichier AC qui n'utilise aucune fonctionnalité matérielle MCU (et ne comprend donc pas pic.h) peut utiliser le nom PORTBà ses propres fins. Ce n'est pas une bonne idée de le faire exprès, mais lorsque vous développez, par exemple, une bibliothèque mathématique agnostique MCU, vous seriez surpris de la facilité avec laquelle il est facile de réutiliser accidentellement un nom qui est utilisé par l'un des MCU.

Dmitry Grigoryev
la source
"vous seriez surpris de la facilité avec laquelle il est facile de réutiliser accidentellement un nom utilisé par l'un des MCU" - j'ose espérer que toutes les bibliothèques mathématiques n'utilisent que des noms en minuscules et que tous les environnements MCU n'utilisent que des majuscules pour le registre noms.
vsz
@vsz LAPACK pour l'un est plein de noms historiques en majuscules.
Dmitry Grigoryev
3

Il existe déjà de bonnes réponses, mais je pense que la cause de la confusion doit être abordée simplement et directement:

La déclaration PORTB n'est pas la norme C. C'est une extension du langage de programmation C qui ne fonctionne qu'avec le compilateur PIC. L'extension est nécessaire car les PIC n'ont pas été conçus pour prendre en charge C.

L'utilisation du staticmot - clé ici est déroutante car vous n'utiliseriez jamais de staticcette façon dans du code normal. Pour une variable globale, vous utiliseriez externdans l'en-tête, non static. Mais PORTB n'est pas une variable normale . C'est un hack qui indique au compilateur d'utiliser des instructions d'assemblage spéciales pour le registre IO. Déclarer PORTB staticaide le compilateur à faire ce qu'il faut.

Lorsqu'il est utilisé à la portée du fichier, staticlimite la portée de la variable ou de la fonction à ce fichier. "Fichier" signifie le fichier C et tout ce qui y est copié par le préprocesseur. Lorsque vous utilisez #include, vous copiez du code dans votre fichier C. C'est pourquoi l'utilisation staticdans un en-tête n'a aucun sens - au lieu d'une variable globale, chaque fichier qui #inclut l'en-tête obtiendrait une copie distincte de la variable.

Contrairement à la croyance populaire, staticsignifie toujours la même chose: allocation statique avec une portée limitée. Voici ce qui arrive aux variables avant et après leur déclaration static:

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

Ce qui le rend confus, c'est que le comportement par défaut des variables dépend de l'endroit où elles sont définies.

Adam Haun
la source
2

La raison pour laquelle le fichier principal peut voir la définition de port "statique" est à cause de la directive #include. Cette directive équivaut à insérer tout le fichier d'en-tête dans votre code source sur la même ligne que la directive elle-même.

Le compilateur de micropuces XC8 traite les fichiers .c et .h exactement de la même manière afin que vous puissiez mettre vos définitions de variables dans l'une ou l'autre.

Normalement, un fichier d'en-tête contient une référence "externe" à des variables définies ailleurs (généralement un fichier .c).

Les variables de port devaient être spécifiées à des adresses mémoire spécifiques qui correspondent au matériel réel. Une définition réelle (non externe) devait donc exister quelque part.

Je ne peux que deviner pourquoi Microchip Corporation a choisi de mettre les définitions réelles dans le fichier .h. Une supposition probable est qu'ils voulaient juste un fichier (.h) au lieu de 2 (.h et .c) (pour faciliter les choses pour l'utilisateur).

Mais si vous placez les définitions de variables réelles dans un fichier d'en-tête et que vous incluez ensuite cet en-tête dans plusieurs fichiers source, l'éditeur de liens se plaindra que les variables sont définies plusieurs fois.

La solution consiste à déclarer les variables comme statiques, puis chaque définition est traitée comme locale pour ce fichier objet et l'éditeur de liens ne se plaindra pas.

user4574
la source