Konzept des statischen Schlüsselworts aus der Perspektive von eingebettetem C.


9
static volatile unsigned char   PORTB   @ 0x06;

Dies ist eine Codezeile in einer PIC-Mikrocontroller-Headerdatei. Der @Operator wird verwendet, um den PORTB-Wert in der Adresse zu speichern. Dies 0x06ist ein Register im PIC-Controller, das PORTB darstellt. Bis zu diesem Punkt habe ich eine klare Idee.

Diese Zeile wird als globale Variable in einer Header-Datei ( .h) deklariert . Nach dem, was ich über die C-Sprache erfahren habe, ist eine "statische globale Variable" für keine andere Datei sichtbar - oder statische globale Variablen / Funktionen können einfach nicht außerhalb der aktuellen Datei verwendet werden.

Wie kann dieses Schlüsselwort PORTBdann für meine Hauptquelldatei und viele andere Header-Dateien sichtbar sein, die ich manuell erstellt habe?

In meiner Hauptquelldatei habe ich nur die Header-Datei hinzugefügt. #include pic.hHat dies etwas mit meiner Frage zu tun?


2
Kein Problem mit der Frage, aber falscher SE-Abschnitt, fürchte ich
Gommer

static wird normalerweise in einer Funktion verwendet, um anzugeben, dass die Variable einmal erstellt wird und ihren Wert von einer Ausführung einer Funktion zur nächsten beibehält. Eine globale Variable wird außerhalb einer Funktion erstellt, sodass sie überall sichtbar ist. statisch global macht nicht wirklich Sinn.
Finbarr

8
@ Finbarr Falsch. staticGlobale Elemente sind in der gesamten einzelnen Kompilierungseinheit sichtbar und werden nicht darüber hinaus exportiert. Sie sind den privateMitgliedern einer Klasse in OOP sehr ähnlich . Dh jede Variable, die zwischen verschiedenen Funktionen innerhalb einer Kompilierungseinheit geteilt werden muss, aber außerhalb dieser Cu nicht sichtbar sein soll, sollte wirklich sein static. Dies reduziert auch das "Clobbering" des globalen Namespace des Programms.
JimmyB

2
Zu "Der @ -Operator wird verwendet, um den PORTB-Wert in der Adresse 0x06 zu speichern" . "Ja wirklich?" Ist nicht mehr wie "Der @ -Operator wird verwendet, um die Variable" PORTB "unter der absoluten Speicheradresse 0x06 zu speichern" ?
Peter Mortensen

Antworten:


20

Das Schlüsselwort 'statisch' in C hat zwei grundlegend unterschiedliche Bedeutungen.

Umfang einschränken

In diesem Zusammenhang wird 'static' mit 'extern' gepaart, um den Umfang einer Variablen oder eines Funktionsnamens zu steuern. Statisch bewirkt, dass der Name der Variablen oder Funktion nur innerhalb einer einzelnen Kompilierungseinheit und nur für Code verfügbar ist, der nach der Deklaration / Definition im Text der Kompilierungseinheit vorhanden ist.

Diese Einschränkung selbst bedeutet nur dann etwas, wenn Sie mehr als eine Kompilierungseinheit in Ihrem Projekt haben. Wenn Sie nur eine Kompilierungseinheit haben, macht sie immer noch Dinge, aber diese Effekte sind meistens sinnlos (es sei denn, Sie möchten in Objektdateien graben, um zu lesen, was der Compiler generiert hat.)

Wie bereits erwähnt, wird dieses Schlüsselwort in diesem Kontext mit dem Schlüsselwort 'extern' gepaart, was das Gegenteil bewirkt - indem der Name der Variablen oder Funktion mit demselben Namen verknüpft wird, der in anderen Kompilierungseinheiten gefunden wird. Sie können also "statisch" so betrachten, dass die Variable oder der Name in der aktuellen Kompilierungseinheit gefunden werden muss, während "extern" die Verknüpfung zwischen Kompilierungseinheiten zulässt.

Statische Lebensdauer

Statische Lebensdauer bedeutet, dass die Variable während der gesamten Dauer des Programms vorhanden ist (wie lange das auch sein mag). Wenn Sie eine Variable innerhalb einer Funktion mit 'statisch' deklarieren / definieren, bedeutet dies, dass die Variable irgendwann vor ihrer ersten Verwendung erstellt wird ( Dies bedeutet, dass jedes Mal, wenn ich es erlebt habe, die Variable vor dem Start von main () erstellt und danach nicht zerstört wird. Nicht einmal, wenn die Ausführung der Funktion abgeschlossen ist und sie zu ihrem Aufrufer zurückkehrt. Und genau wie statische Lebensdauervariablen, die außerhalb von Funktionen deklariert wurden, werden sie im selben Moment - bevor main () startet - auf eine semantische Null (wenn keine Initialisierung angegeben ist) oder auf einen bestimmten expliziten Wert (falls angegeben) initialisiert.

Dies unterscheidet sich von Funktionsvariablen vom Typ 'auto', die bei jeder Eingabe der Funktion neu erstellt werden (oder als ob sie neu wären) und dann beim Beenden der Funktion zerstört werden (oder als ob sie zerstört würden).

Im Gegensatz zu den Auswirkungen der Anwendung von "statisch" auf eine Variablendefinition außerhalb einer Funktion, die sich direkt auf ihren Gültigkeitsbereich auswirkt, hat die Deklaration einer Funktionsvariablen (offensichtlich innerhalb eines Funktionskörpers) als "statisch" keine Auswirkungen auf ihren Gültigkeitsbereich. Der Umfang wird dadurch bestimmt, dass er innerhalb eines Funktionskörpers definiert wurde. In Funktionen definierte statische Lebensdauervariablen haben denselben Gültigkeitsbereich wie andere in Funktionskörpern definierte 'Auto'-Variablen - Funktionsumfang.

Zusammenfassung

Das Schlüsselwort "statisch" hat also unterschiedliche Kontexte mit "sehr unterschiedlichen Bedeutungen". Der Grund, warum es auf zwei Arten verwendet wurde, war, die Verwendung eines anderen Schlüsselworts zu vermeiden. (Es gab eine lange Diskussion darüber.) Es wurde die Ansicht vertreten, dass Programmierer die Verwendung tolerieren könnten, und der Wert, ein weiteres Schlüsselwort in der Sprache zu vermeiden, war wichtiger (als ansonsten Argumente).

(Alle außerhalb von Funktionen deklarierten Variablen haben eine statische Lebensdauer und benötigen nicht das Schlüsselwort 'static', um dies zu erfüllen. Auf diese Weise wurde das dort zu verwendende Schlüsselwort freigegeben, um etwas völlig anderes zu bedeuten: 'nur in einer einzigen Kompilierung sichtbar Einheit. 'Es ist eine Art Hack.)

Besonderer Hinweis

statisches flüchtiges vorzeichenloses Zeichen PORTB @ 0x06;

Das Wort "statisch" sollte hier so interpretiert werden, dass der Linker nicht versucht, mehrere Vorkommen von PORTB abzugleichen , die in mehr als einer Kompilierungseinheit gefunden werden können (vorausgesetzt, Ihr Code hat mehr als eine).

Es verwendet eine spezielle (nicht portierbare) Syntax, um den "Speicherort" (oder den numerischen Wert des Etiketts, der normalerweise eine Adresse ist) von PORTB anzugeben. Der Linker erhält also die Adresse und muss keine dafür finden. Wenn Sie zwei Kompilierungseinheiten hätten, die diese Zeile verwenden, würden sie ohnehin jeweils auf dieselbe Stelle zeigen. Hier muss es also nicht als "extern" bezeichnet werden.

Hätten sie "extern" verwendet, könnte dies ein Problem darstellen. Der Linker könnte dann mehrere Verweise auf PORTB sehen (und versuchen, sie abzugleichen), die in mehreren Zusammenstellungen gefunden wurden. Wenn alle eine Adresse wie diese angeben und die Adressen aus irgendeinem Grund NICHT gleich sind [Fehler?], Was soll sie dann tun? Beschweren? Oder? (Technisch gesehen würde bei 'extern' die Faustregel lauten, dass nur EINE Kompilierungseinheit den Wert angeben würde und die anderen nicht.)

Es ist einfach einfacher, es als "statisch" zu kennzeichnen, um zu vermeiden, dass sich der Linker über Konflikte Sorgen macht, und einfach die Schuld für Fehler bei nicht übereinstimmenden Adressen zu geben, wenn jemand die Adresse in etwas geändert hat, das es nicht sein sollte.

In beiden Fällen wird die Variable als "statische Lebensdauer" behandelt. (Und "flüchtig".)

Eine Deklaration ist keine Definition , aber alle Definitionen sind Deklarationen

In C erstellt eine Definition ein Objekt. Es erklärt es auch. Eine Deklaration erstellt jedoch normalerweise kein Objekt (siehe Aufzählungszeichen unten).

Das Folgende sind Definitionen und Erklärungen:

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

Das Folgende sind keine Definitionen, sondern nur Erklärungen:

extern int b;
extern int f();

Beachten Sie, dass die Deklarationen kein tatsächliches Objekt erstellen. Sie deklarieren nur die Details dazu, die der Compiler dann verwenden kann, um korrekten Code zu generieren und gegebenenfalls Warn- und Fehlermeldungen bereitzustellen.

  • Oben sage ich "normalerweise" mit Rat und Tat. In einigen Fällen kann eine Deklaration ein Objekt erstellen und wird daher vom Linker (niemals vom Compiler) zu einer Definition heraufgestuft. Selbst in diesem seltenen Fall glaubt der C-Compiler, dass die Deklaration nur eine Deklaration ist. Es ist die Linker-Phase, die alle notwendigen Werbeaktionen für eine Erklärung vornimmt. Denken Sie sorgfältig daran.

    Sollte sich in den obigen Beispielen herausstellen, gibt es nur Deklarationen für ein "extern int b"; In allen verknüpften Kompilierungseinheiten trägt der Linker die Verantwortung für die Erstellung einer Definition. Beachten Sie, dass dies ein Ereignis zur Verbindungszeit ist. Der Compiler ist sich während der Kompilierung überhaupt nicht bewusst. Es kann nur zum Zeitpunkt der Verknüpfung festgestellt werden, ob eine Deklaration dieses Typs am meisten beworben wird.

    Dem Compiler ist bekannt, dass "static int a;" kann vom Linker zur Linkzeit nicht hochgestuft werden, daher ist dies tatsächlich eine Definition zur Kompilierungszeit .


3
Tolle Antwort, +1! Nur ein kleiner Punkt: Sie könnten verwendet haben extern, und es wäre die richtigere C Art und Weise tun: Deklarieren Sie die Variable externin einer Header - Datei inlcuded mehrfach im Programm werden und definieren es in einigen Nicht-Header - Datei zu erstellenden und genau einmal verlinkt. Immerhin PORTB ist angeblich genau sein , eine Instanz der Variablen , auf die unterschiedliche CUs beziehen. Die Verwendung von statichier ist also eine Art Verknüpfung, die sie verwendet haben, um zu vermeiden, dass zusätzlich zur Header-Datei eine weitere .c-Datei benötigt wird.
JimmyB

Ich würde auch bemerken, dass eine statische Variable, die innerhalb einer Funktion deklariert ist, nicht über Funktionsaufrufe hinweg geändert wird, was für Funktionen nützlich sein kann, die eine Art von Statusinformationen beibehalten müssen (ich habe sie in der Vergangenheit speziell für diesen Zweck verwendet).
Peter Smith

@ Peter Ich glaube ich habe das gesagt. Aber vielleicht nicht so gut, wie Sie es gerne hätten?
Jonk

@JimmyB Nein, sie hätten stattdessen nicht 'extern' für Funktionsvariablendeklarationen verwenden können, die sich wie 'statisch' verhalten. 'extern' ist bereits eine Option für Variablendeklarationen (keine Definitionen) innerhalb von Funktionskörpern und dient einem anderen Zweck - dem Zugriff auf Variablen, die außerhalb einer Funktion definiert sind. Aber es ist möglich, dass ich Ihren Standpunkt auch falsch verstehe.
Jonk

1
@ JimmyB Externe Verknüpfung wäre definitiv möglich, obwohl ich nicht weiß, ob es "richtiger" ist. Eine Überlegung ist, dass der Compiler möglicherweise optimierten Code ausgeben kann, wenn die Informationen in der Übersetzungseinheit gefunden werden. In eingebetteten Szenarien kann das Speichern von Zyklen für jede E / A-Anweisung eine große Sache sein.
Cort Ammon

9

statics sind außerhalb der aktuellen Kompilierungseinheit oder "Übersetzungseinheit" nicht sichtbar . Dies ist nicht dasselbe wie dieselbe Datei .

Beachten Sie, dass Sie enthalten die Header - Datei in jede Quelldatei in dem Sie die Variablen müssen im Header deklariert. Diese Aufnahme macht die Header-Datei zu einem Teil der aktuellen Übersetzungseinheit und (einer Instanz von) der darin sichtbaren Variablen.


Danke für deine Antwort. "compilation Unit", Entschuldigung, ich verstehe nicht, können Sie diesen Begriff erklären. Lassen Sie mich noch eine Frage stellen, auch wenn wir Variablen und Funktionen verwenden möchten, die in einer anderen Datei geschrieben sind, müssen wir diese Datei zuerst in unsere Hauptquellendatei aufnehmen. Warum dann das Schlüsselwort "static volatile" in dieser Header-Datei?
Electro Voyager



3
@ElectroVoyager; Wenn Sie denselben Header mit einer statischen Deklaration in mehrere c-Quelldateien aufnehmen, hat jede dieser Dateien eine statische Variable mit demselben Namen, die jedoch nicht dieselbe Variable ist .
Peter Smith

2
Vom @ JimmyB-Link: Files included by using the #include preprocessor directive become part of the compilation unit.Wenn Sie Ihre Header-Datei (.h) in eine .c-Datei aufnehmen, stellen Sie sich vor, Sie fügen den Inhalt des Headers in die Quelldatei ein. Dies ist nun Ihre Kompilierungseinheit. Wenn Sie diese statische Variable oder Funktion in einer .c-Datei deklarieren, können Sie sie nur in derselben Datei verwenden, die am Ende eine weitere Kompilierungseinheit darstellt.
Gustavovelascoh

5

Ich werde versuchen, Kommentare und die Antwort von @ JimmyB mit einem erklärenden Beispiel zusammenzufassen:

Angenommen, diese Dateien:

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

Sie können den Code mit kompilieren und ausführen gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test indem Sie den statischen Header oder gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testden nicht statischen Header verwenden.

Beachten Sie, dass hier zwei Kompilierungseinheiten vorhanden sind: static_src und static_test. Wenn Sie die statische Version des Headers ( -DUSE_STATIC=1) verwenden, ist für jede Kompilierungseinheit eine Version von varund say_helloverfügbar. Dies bedeutet, dass beide Einheiten sie verwenden können. Überprüfen Sie dies jedoch, obwohl die var_add_one()Funktion ihre var Variable erhöht , wenn die Hauptfunktion ihre var Variable druckt ist es immer noch 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

Wenn Sie nun versuchen, den Code mit einer nicht statischen Version zu kompilieren und auszuführen (-DUSE_STATIC=0 ) , wird aufgrund der doppelten Variablendefinition ein Verknüpfungsfehler ausgegeben:

$ 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

Ich hoffe, dies könnte Ihnen helfen, diese Angelegenheit zu klären.


4

#include pic.hbedeutet ungefähr "Kopieren Sie den Inhalt von pic.h in die aktuelle Datei". Als Ergebnis jede Datei, die enthältpic.h erhält eine eigene lokale Definition von PORTB.

Vielleicht fragen Sie sich, warum es keine einheitliche globale Definition von gibt PORTB. Der Grund ist ganz einfach: Sie können eine globale Variable nur in einer C-Datei definieren. Wenn Sie also PORTBin mehreren Dateien in Ihrem Projekt verwenden möchten, benötigen Sie pic.heine Deklaration von PORTBund pic.cmit ihrer Definition . Wenn Sie für jede C-Datei eine eigene Kopie definieren, wird PORTBdie Codeerstellung einfacher, da Sie keine nicht geschriebenen Projektdateien in Ihre Projektdateien aufnehmen müssen.

Ein zusätzlicher Vorteil statischer Variablen gegenüber globalen Variablen besteht darin, dass Sie weniger Namenskonflikte erhalten. Eine AC-Datei, die keine MCU-Hardwarefunktionen verwendet (und daher keine enthält pic.h), kann den Namen PORTBfür ihren eigenen Zweck verwenden. Nicht, dass es eine gute Idee wäre, dies absichtlich zu tun, aber wenn Sie beispielsweise eine MCU-unabhängige Mathematikbibliothek entwickeln, werden Sie überrascht sein, wie einfach es ist, versehentlich einen Namen wiederzuverwenden, der von einer der MCUs da draußen verwendet wird.


"Sie wären überrascht, wie einfach es ist, versehentlich einen Namen wiederzuverwenden, der von einer der MCUs da draußen verwendet wird" - ich wage zu hoffen, dass alle Mathematikbibliotheken nur Kleinbuchstaben verwenden und dass alle MCU-Umgebungen nur Großbuchstaben für die Registrierung verwenden Namen.
vsz

@vsz LAPACK für einen ist voll von historischen All-Caps-Namen.
Dmitry Grigoryev

3

Es gibt bereits einige gute Antworten, aber ich denke, die Ursache der Verwirrung muss einfach und direkt angegangen werden:

Die PORTB-Deklaration ist nicht Standard C. Sie ist eine Erweiterung der Programmiersprache C, die nur mit dem PIC-Compiler funktioniert. Die Erweiterung ist erforderlich, da PICs nicht zur Unterstützung von C entwickelt wurden.

Die Verwendung des staticSchlüsselworts hier ist verwirrend, da Sie dies staticim normalen Code niemals so verwenden würden . Für eine globale Variable würden Sie nicht externim Header verwenden static. PORTB ist jedoch keine normale Variable . Es ist ein Hack, der den Compiler anweist, spezielle Montageanweisungen für Register-E / A zu verwenden. Das Deklarieren von PORTB statichilft dem Compiler, das Richtige zu tun.

staticBeschränkt bei Verwendung im Dateibereich den Bereich der Variablen oder Funktion auf diese Datei. "Datei" bezeichnet die C-Datei und alles, was vom Präprozessor in sie kopiert wurde. Wenn Sie #include verwenden, kopieren Sie Code in Ihre C-Datei. Deshalb verwendenstatic in einem Header keinen Sinn. Anstelle einer globalen Variablen erhält jede Datei, die den Header enthält, eine separate Kopie der Variablen.

Entgegen der landläufigen Meinung staticbedeutet dies immer dasselbe: statische Zuordnung mit begrenztem Umfang. Folgendes passiert mit Variablen vor und nach der Deklaration 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) |
+------------------------+-------------------+--------------------+

Was es verwirrend macht, ist, dass das Standardverhalten von Variablen davon abhängt, wo sie definiert sind.


2

Der Grund, warum die Hauptdatei die "statische" Portdefinition sehen kann, liegt in der Direktive #include. Diese Anweisung entspricht dem Einfügen der gesamten Header-Datei in Ihren Quellcode in derselben Zeile wie die Anweisung selbst.

Der XC8-Compiler des Mikrochips behandelt .c- und .h-Dateien genauso, sodass Sie Ihre Variablendefinitionen in eine der beiden Dateien einfügen können.

Normalerweise enthält eine Header-Datei einen "externen" Verweis auf Variablen, die an anderer Stelle definiert sind (normalerweise eine .c-Datei).

Die Portvariablen mussten an bestimmten Speicheradressen angegeben werden, die der tatsächlichen Hardware entsprechen. Eine tatsächliche (nicht externe) Definition musste also irgendwo existieren.

Ich kann nur raten, warum sich die Microchip Corporation dafür entschieden hat, die tatsächlichen Definitionen in die .h-Datei aufzunehmen. Eine wahrscheinliche Vermutung ist, dass sie nur eine Datei (.h) anstelle von 2 (.h und .c) wollten (um dem Benutzer die Arbeit zu erleichtern).

Wenn Sie jedoch die tatsächlichen Variablendefinitionen in eine Header-Datei einfügen und diesen Header dann in mehrere Quelldateien aufnehmen, beschwert sich der Linker darüber, dass die Variablen mehrfach definiert sind.

Die Lösung besteht darin, die Variablen als statisch zu deklarieren. Anschließend wird jede Definition als lokal für diese Objektdatei behandelt, und der Linker beschwert sich nicht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.