Concetto della parola chiave statica dal punto di vista del C incorporato


9
static volatile unsigned char   PORTB   @ 0x06;

Questa è una riga di codice in un file di intestazione del microcontrollore PIC. L' @operatore viene utilizzato per memorizzare il valore PORTB all'interno dell'indirizzo 0x06, che è un registro all'interno del controller PIC che rappresenta PORTB. Fino a questo punto, ho un'idea chiara.

Questa riga è dichiarata come variabile globale all'interno di un file header ( .h). Quindi, da quello che sono venuto a conoscenza del linguaggio C, una "variabile globale statica" non è visibile a nessun altro file - o, semplicemente, le variabili / funzioni globali statiche non possono essere utilizzate al di fuori del file corrente.

Quindi, come può questa parola chiave PORTBessere visibile al mio file sorgente principale e a molti altri file di intestazione che ho creato manualmente?

Sul mio file sorgente principale, ho aggiunto solo il file header. #include pic.hHa qualcosa a che fare con la mia domanda?


2
nessun problema con la domanda, ma temo che la sezione SE sbagliata
gommer

static viene normalmente utilizzato all'interno di una funzione per specificare che la variabile viene creata una volta e mantiene il suo valore da un'esecuzione di una funzione alla successiva. una variabile globale è una creata al di fuori di qualsiasi funzione in modo che sia visibile ovunque. statico globale non ha davvero senso.
Finbarr,

8
@Finbarr Wrong. statici globi sono visibili all'interno dell'intera singola unità di compilazione e non vengono esportati oltre. Sono molto simili ai privatemembri di una classe in OOP. Vale a dire ogni variabile che deve essere condivisa tra diverse funzioni all'interno di un'unità di compilazione ma che non dovrebbero essere visibili al di fuori di quel cu dovrebbe essere static. Ciò riduce anche il "clobbering" dello spazio dei nomi globale del programma.
JimmyB,

2
Ri "L'operatore @ viene utilizzato per memorizzare il valore PORTB all'interno dell'indirizzo 0x06" . Veramente? Non è più come "L'operatore @ viene utilizzato per memorizzare la variabile" PORTB "nell'indirizzo di memoria assoluto 0x06" ?
Peter Mortensen,

Risposte:


20

La parola chiave "statico" in C ha due significati fondamentalmente diversi.

Ambito di limitazione

In questo contesto, "static" si accoppia con "extern" per controllare l'ambito di una variabile o il nome di una funzione. Statico fa sì che il nome della variabile o della funzione sia disponibile solo all'interno di una singola unità di compilazione e disponibile solo per il codice esistente dopo la dichiarazione / definizione all'interno del testo dell'unità di compilazione.

Questa stessa limitazione in realtà significa solo qualcosa se e solo se hai più di una unità di compilazione nel tuo progetto. Se hai una sola unità di compilazione, fa comunque cose ma quegli effetti sono per lo più inutili (a meno che non ti piaccia scavare in file oggetto per leggere ciò che il compilatore ha generato.)

Come notato, questa parola chiave in questo contesto si accoppia con la parola chiave "extern", che fa il contrario - rendendo la variabile o il nome della funzione collegabile con lo stesso nome trovato in altre unità di compilazione. Quindi puoi considerare "statico" come se fosse necessario trovare la variabile o il nome nell'attuale unità di compilazione, mentre "extern" consente il collegamento tra unità di compilazione incrociata.

Durata statica

La durata statica significa che la variabile esiste per tutta la durata del programma (per quanto lunga sia.) Quando si utilizza "statico" per dichiarare / definire una variabile all'interno di una funzione, significa che la variabile viene creata prima del suo primo utilizzo ( il che significa, ogni volta che l'ho sperimentato, che la variabile viene creata prima dell'inizio di main () e non viene distrutta in seguito. Nemmeno quando l'esecuzione della funzione è completata e ritorna al suo chiamante. E proprio come le variabili di durata statica dichiarate al di fuori delle funzioni, vengono inizializzate nello stesso momento - prima che main () inizi - su uno zero semantico (se non viene fornita l'inizializzazione) o su un valore esplicito specificato, se indicato.

Ciò è diverso dalle variabili di funzione di tipo "auto", che vengono create nuove (o, come se fossero nuove) ogni volta che la funzione viene immessa e quindi vengono distrutte (o, come se fossero state distrutte) all'uscita della funzione.

A differenza dell'impatto dell'applicazione di "statico" su una definizione variabile al di fuori di una funzione, che influisce direttamente sul suo ambito, dichiarare una variabile di funzione (all'interno di un corpo di funzione, ovviamente) come "statico" non ha alcun impatto sul suo ambito. L'ambito è determinato dal fatto che è stato definito all'interno di un corpo di funzione. Le variabili di durata statiche definite all'interno delle funzioni hanno lo stesso ambito delle altre variabili "auto" definite all'interno dei corpi delle funzioni - ambito delle funzioni.

Sommario

Quindi la parola chiave "statica" ha contesti diversi con ciò che equivale a "significati molto diversi". Il motivo per cui è stato utilizzato in due modi, come questo, è stato quello di evitare di usare un'altra parola chiave. (Ci fu una lunga discussione a riguardo.) Si pensava che i programmatori potessero tollerare l'uso e il valore di evitare l'ennesima parola chiave nella lingua era più importante (rispetto agli argomenti altrimenti).

(Tutte le variabili dichiarate al di fuori delle funzioni hanno una durata statica e non hanno bisogno della parola chiave "statica" per renderla vera. Quindi questo tipo di parola chiave liberata per essere usata lì significa qualcosa di completamente diverso: "visibile solo in una singola compilazione unità. "È una specie di hack.)

Nota specifica

carattere statico volatile senza segno PORTB @ 0x06;

La parola "statico" qui dovrebbe essere interpretata nel senso che il linker non tenterà di far corrispondere più occorrenze di PORTB che possono essere trovate in più di una unità di compilazione (supponendo che il codice ne abbia più di una).

Utilizza una sintassi speciale (non portatile) per specificare la "posizione" (o il valore numerico dell'etichetta che di solito è un indirizzo) di PORTB. Quindi al linker viene assegnato l'indirizzo e non è necessario trovarne uno. Se avessi due unità di compilazione che usano questa linea, ognuna finirebbe per indicare lo stesso posto. Quindi non c'è bisogno di etichettarlo come "esterno", qui.

Se avessero usato "esternamente", ciò avrebbe potuto costituire un problema. Il linker sarebbe quindi in grado di vedere (e tenterebbe di abbinare) più riferimenti a PORTB trovati in più compilazioni. Se tutti specificano un indirizzo come questo, e gli indirizzi NON sono gli stessi per qualche motivo [errore?], Allora cosa dovrebbe fare? Lamentarsi? O? (Tecnicamente, con 'extern' la regola empirica sarebbe che solo UNA unità di compilazione specifica il valore e le altre no.)

È solo più facile etichettarlo come "statico", evitando di preoccupare il linker dei conflitti e semplicemente dare la colpa per eventuali errori per indirizzi errati su chiunque abbia cambiato l'indirizzo in qualcosa che non dovrebbe essere.

In entrambi i casi, la variabile viene considerata come "a vita statica". (E 'volatile'.)

Una dichiarazione non è una definizione , ma tutte le definizioni sono dichiarazioni

In C, una definizione crea un oggetto. Lo dichiara anche. Ma una dichiarazione di solito (vedi la nota in basso) non crea un oggetto.

Di seguito sono riportate definizioni e dichiarazioni:

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

Le seguenti non sono definizioni, ma solo dichiarazioni:

extern int b;
extern int f();

Si noti che le dichiarazioni non creano un oggetto reale. Dichiarano solo i dettagli al riguardo, che il compilatore può quindi utilizzare per aiutare a generare il codice corretto e per fornire messaggi di avviso e di errore, come appropriato.

  • Sopra, dico "di solito", in modo consapevole. In alcuni casi, una dichiarazione può creare un oggetto ed è quindi promossa a una definizione dal linker (mai dal compilatore). Quindi, anche in questo raro caso, il compilatore C pensa ancora che la dichiarazione sia solo una dichiarazione. È la fase del linker che rende necessarie eventuali promozioni di alcune dichiarazioni. Tienilo bene a mente.

    Negli esempi precedenti, se dovesse risultare che ci sono solo dichiarazioni per un "extern int b;" in tutte le unità di compilazione collegate, quindi il linker ha la responsabilità di creare una definizione. Tieni presente che si tratta di un evento di collegamento temporale. Il compilatore è completamente inconsapevole, durante la compilazione. Può essere determinato solo al momento del collegamento, se viene promossa una dichiarazione di questo tipo.

    Il compilatore è consapevole che "static int a;" non può essere promosso dal linker al momento del collegamento, quindi in realtà questa è una definizione al momento della compilazione .


3
Ottima risposta, +1! Solo un punto minore: avrebbero potuto usare extern, e sarebbe il modo C più appropriato per farlo: dichiarare la variabile externin un file di intestazione da includere più volte nel programma e definirla in un file non di intestazione da compilare e collegato esattamente una volta. Dopo tutto, PORTB è dovrebbe essere esattamente un'istanza della variabile a cui diversi CU possono fare riferimento. Quindi l'uso di qui è una specie di scorciatoia che hanno preso per evitare di aver bisogno di un altro file .c oltre al file di intestazione. static
JimmyB,

Vorrei anche notare che una variabile statica dichiarata all'interno di una funzione non viene modificata tra le chiamate di funzione che possono essere utili per le funzioni che devono conservare una sorta di informazione sullo stato (l'ho usata appositamente per questo scopo in passato).
Peter Smith,

@Peter Penso di averlo detto. Ma forse non come ti sarebbe piaciuto?
Jon

@JimmyB No, non avrebbero potuto usare 'extern', invece, per le dichiarazioni delle variabili di funzione che si comportano come 'statico'. 'extern' è già un'opzione per dichiarazioni di variabili (non definizioni) all'interno di corpi di funzioni e ha uno scopo diverso: fornire un accesso al tempo di collegamento a variabili definite al di fuori di qualsiasi funzione. Ma è possibile che fraintenda anche il tuo punto.
Jon

1
Il collegamento di @JimmyB Extern sarebbe sicuramente possibile, anche se non so se sia "più appropriato". Una considerazione è che il compilatore potrebbe essere in grado di emettere un codice più ottimizzato se le informazioni vengono trovate nell'unità di traduzione. Per gli scenari incorporati, salvare cicli su ogni istruzione IO può essere un grosso problema.
Cort Ammon,

9

staticgli s non sono visibili al di fuori dell'unità di compilazione corrente o "unità di traduzione". Questo non è lo stesso dello stesso file .

Si noti che si include il file di intestazione in qualsiasi file di origine in cui potrebbero essere necessarie le variabili dichiarate nell'intestazione. Questa inclusione rende il file di intestazione parte dell'unità di traduzione corrente e (un'istanza di) la variabile visibile al suo interno.


Grazie per la tua risposta. "Unità di compilazione", mi dispiace, non capisco, puoi spiegare questo termine. Lascia che ti faccia un'altra domanda, anche se vogliamo usare variabili e funzioni scritte all'interno di un altro file, dobbiamo prima INCLUDERE quel file nel nostro FILE SORGENTE principale. Quindi perché la parola chiave "statica volatile" in quel file di intestazione.
Electro Voyager



3
@ElectroVoyager; se includi la stessa intestazione contenente una dichiarazione statica in più file sorgente c, ciascuno di questi file avrà una variabile statica con lo stesso nome, ma non sono la stessa variabile .
Peter Smith,

2
Dal link @JimmyB: Files included by using the #include preprocessor directive become part of the compilation unit.quando includi il tuo file di intestazione (.h) in un file .c, pensalo come l'inserimento del contenuto dell'intestazione nel file di origine, e ora questa è la tua unità di compilazione. Se dichiari quella variabile statica o la funzione in un file .c, puoi usarli solo nello stesso file, che alla fine sarà un'altra unità di compilazione.
gustavovelascoh

5

Proverò a riassumere i commenti e la risposta di @ JimmyB con un esempio esplicativo:

Supponiamo che questo set di file:

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();
}

È possibile compilare ed eseguire il codice usando gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_testper usare l'intestazione statica o gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testper usare l'intestazione non statica.

Si noti che qui sono presenti due unità di compilazione: static_src e static_test. Quando si utilizza la versione statica dell'intestazione ( -DUSE_STATIC=1), una versione di vare say_hellosarà disponibile per ogni unità di compilazione, vale a dire entrambe le unità possono usarle, ma verificare che anche se la var_add_one()funzione incrementa la sua var variabile, quando la funzione principale stampa la sua var variabile , è ancora 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

Ora, se si tenta di compilare ed eseguire il codice, utilizzando la versione non statica (-DUSE_STATIC=0 ), verrà generato un errore di collegamento a causa della definizione della variabile duplicata:

$ 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

Spero che questo possa aiutarti a chiarire la questione.


4

#include pic.hsignifica approssimativamente "copiare il contenuto di pic.h nel file corrente". Di conseguenza, ogni file che include pic.hottiene la propria definizione locale di PORTB.

Forse ti chiedi perché non esiste un'unica definizione globale di PORTB. Il motivo è abbastanza semplice: puoi definire solo una variabile globale in un file C, quindi se vuoi usarlo PORTBin più file nel tuo progetto, avresti bisogno pic.hdi una dichiarazione di PORTBe pic.ccon la sua definizione . Consentire a ciascun file C di definire la propria copia di PORTBsemplifica la creazione del codice, poiché non è necessario includere nei file di progetto non scritti.

Un ulteriore vantaggio delle variabili statiche rispetto ai globali è che si ottengono meno conflitti di denominazione. Il file AC che non utilizza alcuna funzionalità hardware MCU (e quindi non include pic.h) può utilizzare il nome PORTBper il proprio scopo. Non che sia una buona idea farlo apposta, ma quando si sviluppa, ad esempio, una libreria matematica agnostica MCU, si sarebbe sorpresi di quanto sia facile riutilizzare accidentalmente un nome utilizzato da uno degli MCU in circolazione.


"Sareste sorpresi di quanto sia facile riutilizzare accidentalmente un nome utilizzato da una delle MCU disponibili" - oso sperare che tutte le librerie matematiche utilizzino solo nomi in minuscolo e che tutti gli ambienti MCU utilizzino solo maiuscole per il registro nomi.
vsz

@vsz LAPACK for one è pieno di nomi storici in maiuscolo.
Dmitry Grigoryev

3

Ci sono già alcune buone risposte, ma penso che la causa della confusione debba essere affrontata in modo semplice e diretto:

La dichiarazione PORTB non è standard C. È un'estensione del linguaggio di programmazione C che funziona solo con il compilatore PIC. L'estensione è necessaria perché i PIC non sono stati progettati per supportare C.

L'uso della staticparola chiave qui è confuso perché non lo useresti mai in staticquesto modo nel normale codice.Per una variabile globale, externuseresti nell'intestazione, no static. Ma PORTB non è una variabile normale . È un hack che dice al compilatore di usare speciali istruzioni di assemblaggio per il registro IO. Dichiarare PORTB staticaiuta a indurre il compilatore a fare la cosa giusta.

Se utilizzato nell'ambito del file, static limita l'ambito della variabile o della funzione a quel file. "File" indica il file C e tutto ciò che viene copiato dal preprocessore. Quando usi #include, stai copiando il codice nel tuo file C. Ecco perché l'uso staticin un'intestazione non ha senso: invece di una variabile globale, ogni file che include # intestazione otterrebbe una copia separata della variabile.

Contrariamente alle credenze popolari, static significa sempre la stessa cosa: allocazione statica con portata limitata. Ecco cosa succede alle variabili prima e dopo essere state dichiarate 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) |
+------------------------+-------------------+--------------------+

Ciò che rende confuso è che il comportamento predefinito delle variabili dipende da dove sono definite.


2

Il motivo per cui il file principale può vedere la definizione della porta "statica" è dovuto alla direttiva #include. Tale direttiva equivale a inserire l'intero file di intestazione nel codice sorgente sulla stessa riga della direttiva stessa.

Il compilatore XC8 del microchip tratta i file .c e .h esattamente allo stesso modo, in modo da poter inserire le definizioni delle variabili in una delle due.

Normalmente un file di intestazione contiene riferimenti "esterni" a variabili definite altrove (di solito un file .c).

Le variabili della porta dovevano essere specificate a specifici indirizzi di memoria che corrispondono all'hardware reale. Quindi una definizione reale (non esterna) doveva esistere da qualche parte.

Posso solo immaginare il motivo per cui la Microchip Corporation ha scelto di inserire le definizioni effettive nel file .h. Una probabile ipotesi è che volessero solo un file (.h) anziché 2 (.h e .c) (per rendere le cose più facili per l'utente).

Ma se inserisci le definizioni delle variabili effettive in un file di intestazione e poi includi quell'intestazione in più file di origine, il linker si lamenterà che le variabili sono definite più volte.

La soluzione è dichiarare le variabili come statiche, quindi ogni definizione viene trattata come locale per quel file oggetto e il linker non si lamenterà.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.