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 0x06
ist 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 PORTB
dann 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.h
Hat dies etwas mit meiner Frage zu tun?
static
Globale Elemente sind in der gesamten einzelnen Kompilierungseinheit sichtbar und werden nicht darüber hinaus exportiert. Sie sind denprivate
Mitgliedern 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 seinstatic
. Dies reduziert auch das "Clobbering" des globalen Namespace des Programms.Antworten:
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
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:
Das Folgende sind keine Definitionen, sondern nur Erklärungen:
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 .
quelle
extern
, und es wäre die richtigere C Art und Weise tun: Deklarieren Sie die Variableextern
in einer Header - Datei inlcuded mehrfach im Programm werden und definieren es in einigen Nicht-Header - Datei zu erstellenden und genau einmal verlinkt. ImmerhinPORTB
ist angeblich genau sein , eine Instanz der Variablen , auf die unterschiedliche CUs beziehen. Die Verwendung vonstatic
hier 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.static
s 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.
quelle
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.Ich werde versuchen, Kommentare und die Antwort von @ JimmyB mit einem erklärenden Beispiel zusammenzufassen:
Angenommen, diese Dateien:
static_test.c:
static.h:
no_static.h:
static_src.c:
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 odergcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test
den 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 vonvar
undsay_hello
verfügbar. Dies bedeutet, dass beide Einheiten sie verwenden können. Überprüfen Sie dies jedoch, obwohl dievar_add_one()
Funktion ihrevar
Variable erhöht , wenn die Hauptfunktion ihrevar
Variable druckt ist es immer noch 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:Ich hoffe, dies könnte Ihnen helfen, diese Angelegenheit zu klären.
quelle
#include pic.h
bedeutet 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 vonPORTB
.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 alsoPORTB
in mehreren Dateien in Ihrem Projekt verwenden möchten, benötigen Siepic.h
eine Deklaration vonPORTB
undpic.c
mit ihrer Definition . Wenn Sie für jede C-Datei eine eigene Kopie definieren, wirdPORTB
die 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 NamenPORTB
fü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.quelle
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
static
Schlüsselworts hier ist verwirrend, da Sie diesstatic
im normalen Code niemals so verwenden würden . Für eine globale Variable würden Sie nichtextern
im Header verwendenstatic
. 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 PORTBstatic
hilft dem Compiler, das Richtige zu tun.static
Beschrä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
static
bedeutet dies immer dasselbe: statische Zuordnung mit begrenztem Umfang. Folgendes passiert mit Variablen vor und nach der Deklarationstatic
:Was es verwirrend macht, ist, dass das Standardverhalten von Variablen davon abhängt, wo sie definiert sind.
quelle
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.
quelle