static volatile unsigned char PORTB @ 0x06;
Dies ist eine Codezeile in einer PIC-Mikrocontroller-Header-Datei. Der @
Operator wird verwendet, um den PORTB-Wert innerhalb der Adresse zu speichern 0x06
, die ein Register innerhalb des PIC-Controllers ist, der PORTB darstellt. Bis hierhin habe ich eine klare Vorstellung.
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 einfach statische globale Variablen / Funktionen können nicht außerhalb der aktuellen Datei verwendet werden.
Wie kann dieses Schlüsselwort dann PORTB
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 das etwas mit meiner Frage zu tun?
Das Schlüsselwort 'static' hat in C zwei grundlegend unterschiedliche Bedeutungen.
In diesem Zusammenhang wird „static“ mit „extern“ gepaart, um den Geltungsbereich eines Variablen- oder Funktionsnamens zu steuern. Static bewirkt, dass der Variablen- oder Funktionsname 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 wirklich etwas, wenn Sie mehr als eine Kompilationseinheit in Ihrem Projekt haben. Wenn Sie nur eine Kompilierungseinheit haben, dann macht es immer noch Dinge, aber diese Effekte sind meistens sinnlos (es sei denn, Sie graben gerne in Objektdateien, um zu lesen, was der Compiler generiert hat.)
Wie bereits erwähnt, paart sich dieses Schlüsselwort in diesem Zusammenhang mit dem Schlüsselwort 'extern', das das Gegenteil bewirkt – indem es den Variablen- oder Funktionsnamen mit demselben Namen verknüpfbar macht, der in anderen Kompilierungseinheiten gefunden wird. Sie können also „statisch“ so betrachten, dass die Variable oder der Name innerhalb der aktuellen Kompilierungseinheit gefunden werden muss, während „extern“ eine Verknüpfung zwischen Kompilierungseinheiten ermöglicht.
Statische Lebensdauer bedeutet, dass die Variable während der gesamten Dauer des Programms existiert (wie lange das auch sein mag). Wenn Sie „statisch“ verwenden, um eine Variable innerhalb einer Funktion zu deklarieren/definieren, bedeutet dies, dass die Variable irgendwann vor ihrer ersten Verwendung erstellt wird ( was bedeutet, jedes Mal, wenn ich es erlebt habe, dass die Variable erstellt wird, bevor main() startet) und danach nicht zerstört wird. Auch nicht, 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 werden, werden sie im selben Moment initialisiert – bevor main() startet – auf eine semantische Null (wenn keine Initialisierung bereitgestellt wird) oder auf einen angegebenen expliziten Wert, falls angegeben.
Dies unterscheidet sich von Funktionsvariablen des Typs 'auto', die bei jedem Aufruf der Funktion neu (oder als ob neu) erstellt und dann beim Beenden der Funktion zerstört (oder als ob sie zerstört würden) werden.
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 Auswirkung auf ihren Gültigkeitsbereich . Der Geltungsbereich wird dadurch bestimmt, dass er innerhalb eines Funktionskörpers definiert wurde. Statische Lebensdauervariablen, die in Funktionen definiert sind, haben denselben Gültigkeitsbereich wie andere „Auto“-Variablen, die in Funktionskörpern definiert sind – den Funktionsbereich.
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 angenommen, dass Programmierer die Verwendung tolerieren könnten und der Wert, ein weiteres Schlüsselwort in der Sprache zu vermeiden, wichtiger war (als andere Argumente).
(Alle Variablen, die außerhalb von Funktionen deklariert werden, haben eine statische Lebensdauer und benötigen das Schlüsselwort 'static' nicht, um dies wahr zu machen. Auf diese Weise wurde das Schlüsselwort freigegeben, das dort verwendet werden soll, um etwas völlig anderes zu bedeuten: 'nur in einer einzigen Kompilierung sichtbar Einheit." Es ist eine Art Hack.)
statisches flüchtiges Zeichen ohne Vorzeichen PORTB @ 0x06;
Das Wort „statisch“ sollte hier so interpretiert werden, dass der Linker nicht versucht, mehrere Vorkommen von PORTB abzugleichen, die möglicherweise in mehr als einer Kompilierungseinheit gefunden werden (vorausgesetzt, Ihr Code hat mehr als eine).
Es verwendet eine spezielle (nicht portierbare) Syntax, um den "Standort" (oder den numerischen Wert des Labels, 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 sowieso auf dieselbe Stelle zeigen. Es ist also nicht nötig, es hier als 'extern' zu kennzeichnen.
Hätten sie 'extern' verwendet, könnte dies ein Problem darstellen. Der Linker wäre dann in der Lage, mehrere Verweise auf PORTB zu sehen (und würde versuchen, sie abzugleichen), die in mehreren Kompilierungen gefunden wurden. Wenn alle eine Adresse wie diese angeben und die Adressen aus irgendeinem Grund NICHT gleich sind [Fehler?], was soll es dann tun? Sich beschweren? Oder? (Technisch gesehen wäre bei 'extern' die Faustregel, dass nur EINE Kompilationseinheit 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 demjenigen zu geben, der die Adresse in etwas geändert hat, das sie nicht sein sollte.
In jedem Fall wird die Variable so behandelt, als hätte sie eine „statische Lebensdauer“. (Und „flüchtig“.)
In C erstellt eine Definition ein Objekt. Das wird auch deklariert. Aber eine Deklaration erzeugt normalerweise kein Objekt (siehe Aufzählungszeichen unten).
Es folgen Definitionen und Deklarationen:
static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }
Die folgenden sind keine Definitionen, sondern nur Deklarationen:
extern int b;
extern int f();
Beachten Sie, dass die Deklarationen kein tatsächliches Objekt erstellen. Sie deklarieren nur die Details darüber, die der Compiler dann verwenden kann, um korrekten Code zu generieren und gegebenenfalls Warn- und Fehlermeldungen bereitzustellen.
Oben sage ich absichtlich "normalerweise". In einigen Fällen kann eine Deklaration ein Objekt erstellen und wird daher vom Linker (niemals vom Compiler) zu einer Definition befördert. Selbst in diesem seltenen Fall denkt der C-Compiler also immer noch, dass die Deklaration nur eine Deklaration ist. Es ist die Linker-Phase, die alle notwendigen Beförderungen einer Deklaration vornimmt. Behalten Sie dies sorgfältig im Hinterkopf.
Sollte sich herausstellen, dass es in den obigen Beispielen nur Deklarationen für ein "extern int b;" gibt. in allen verlinkten Übersetzungseinheiten, dann ist der Linker mit der Erstellung einer Definition beauftragt. Beachten Sie, dass dies ein Ereignis zur Verbindungszeit ist. Der Compiler ist während der Kompilierung völlig unbewusst. Ob eine solche Erklärung am meisten beworben wird, kann erst zum Zeitpunkt der Verlinkung festgestellt werden.
Der Compiler ist sich bewusst, dass "static int a;" kann vom Linker zur Linkzeit nicht hochgestuft werden, daher ist dies tatsächlich eine Definition zur Kompilierzeit .
extern
, und es wäre der richtigere C-Weg, dies zu tun: Deklarieren Sie die Variable extern
in einer Header-Datei, um sie mehrmals in das Programm aufzunehmen, und definieren Sie sie in einer Nicht-Header-Datei, die kompiliert werden soll und genau einmal eingebunden. Schließlich PORTB
soll genau eine Instanz der Variable sein, auf die sich verschiedene cu's beziehen können. Die Verwendung von static
here ist also eine Art Abkürzung, um zu vermeiden, dass zusätzlich zur Header-Datei eine weitere .c-Datei benötigt wird.static
Weg in diesem Fall nur funktioniert, weil er auf der (nicht portierbaren) Erweiterung ( @ 0x06;
) beruht, die es ermöglicht, mehrere (statische) Instanzen einer Variablen einem einzelnen Speicher zuzuweisen Standort. Die Verwendung extern
wäre kein Problem.static
für globale Variablen im Gegensatz zu funktionslokalen Variablen.@ 0x06
Teil der Definition der Variablen ist, nicht ihrer Deklaration. Es kann nur eine Definition einer (nicht statischen) Variablen in einem Programm geben, alles andere führt zur Verbindungszeit (oder davor) zu einem Fehler. Sie können die (nicht statische) Variable mehrfach über deklarierenextern
, aber es kann nur eine Definition geben.static
. Mein Punkt ist nur, dass das, wofür die statische Deklaration (und Definition) in der Header-Datei in diesem Fall verwendet wird, dh das Verweisen auf dieselbe Variable/den gleichen Speicherort aus mehreren .c-Dateien, nur aufgrund der (nicht portierbaren , nicht- Standard) @ 0x06;
Adressbezeichner, während der "richtige" C-Weg darin bestünde, extern
bei der Deklaration(en) und einer separaten, einzelnen Definition der Variablen zu verwenden. Ohne den @ 0x06
Adressbezeichner, everyextern
.extern
wäre kein Problem gewesen, da es dafür sorgt, dass es immer nur eine Definition der Variablen gibt.static
s sind außerhalb der aktuellen Übersetzungseinheit oder "Übersetzungseinheit" nicht sichtbar. Dies ist nicht dasselbe wie dieselbe Datei .
Beachten Sie, dass Sie die Header-Datei in jede Quelldatei einfügen , in der Sie möglicherweise die im Header deklarierten Variablen benötigen. Diese Einbeziehung macht die Header-Datei zu einem Teil der aktuellen Übersetzungseinheit und (einer Instanz von) der darin sichtbaren Variablen.
Files included by using the #include preprocessor directive become part of the compilation unit.
Wenn Sie Ihre Header-Datei (.h) in eine .c-Datei einfügen, stellen Sie sich vor, Sie fügen den Inhalt des Headers in die Quelldatei ein, und jetzt ist dies 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 sein wird.#include
wird als vorverarbeitende Übersetzungseinheit bezeichnet . Nach der Vorverarbeitung wird eine vorverarbeitende Übersetzungseinheit als Übersetzungseinheit bezeichnet .“Ich werde versuchen, die Kommentare und die Antwort von @ JimmyB mit einem erklärenden Beispiel zusammenzufassen:
Angenommen, dieser Satz von 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 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_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, wird eine Version von var
und say_hello
für jede Kompilationseinheit verfügbar sein, das heißt, beide Einheiten können sie verwenden, aber überprüfen Sie, ob die Funktion ihrevar_add_one()
Variable erhöht , wenn die Hauptfunktion ihre Variable ausgibt , es ist immer noch 64: var
var
$ 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 der 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 konnte Ihnen helfen, diese Angelegenheit zu klären.
#include pic.h
bedeutet ungefähr "den Inhalt von pic.h in die aktuelle Datei kopieren". Als Ergebnis pic.h
erhält jede Datei, die enthält, ihre 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 sie also PORTB
in mehreren Dateien in Ihrem Projekt verwenden möchten, müssten Sie pic.h
mit einer Deklaration von PORTB
und pic.c
mit ihrer Definition . Indem Sie jede C-Datei ihre eigene Kopie von definieren lassen, wird PORTB
die Codeerstellung einfacher, da Sie keine Dateien in Ihr Projekt aufnehmen müssen, die Sie nicht geschrieben haben.
Ein zusätzlicher Vorteil von statischen Variablen gegenüber globalen ist, dass Sie weniger Namenskonflikte bekommen. Eine AC-Datei, die keine MCU-Hardwarefunktionen verwendet (und daher keine enthält pic.h
), kann den Namen PORTB
für ihren eigenen Zweck verwenden. Nicht, dass es eine gute Idee wäre, dies absichtlich zu tun, aber wenn Sie zB eine MCU-agnostische Mathematikbibliothek entwickeln, werden Sie überrascht sein, wie einfach es ist, versehentlich einen Namen wiederzuverwenden, der von einer der MCUs da draußen verwendet wird.
Es gibt bereits einige gute Antworten, aber ich denke, die Ursache der Verwirrung muss einfach und direkt angegangen werden:
Die PORTB-Deklaration ist kein Standard-C. Es ist eine Erweiterung der Programmiersprache C, die nur mit dem PIC-Compiler funktioniert. Die Erweiterung wird benötigt, da PICs nicht für die Unterstützung von C entwickelt wurden.
Die Verwendung des static
Schlüsselworts hier ist verwirrend, da Sie es static
in normalem Code niemals so verwenden würden. Für eine globale Variable würden Sie extern
im Header verwenden, nicht static
. Aber PORTB ist keine normale Variable . Es ist ein Hack, der den Compiler anweist, spezielle Assembler-Anweisungen für Register IO zu verwenden. Das Deklarieren von PORTB static
hilft dem Compiler, das Richtige zu tun.
Beschränkt bei Verwendung im Dateibereich static
den Bereich der Variablen oder Funktion auf diese Datei. "Datei" bedeutet die C-Datei und alles, was vom Präprozessor hineinkopiert wurde. Wenn Sie #include verwenden, kopieren Sie Code in Ihre C-Datei. Aus diesem Grund macht die Verwendung static
in einem Header keinen Sinn – statt einer globalen Variable würde jede Datei, die den Header enthält, eine separate Kopie der Variable erhalten.
Bedeutet entgegen der landläufigen Meinung static
immer dasselbe: statische Allokation 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.
Der Grund, warum die Hauptdatei die "statische" Portdefinition sehen kann, liegt an der #include-Direktive. Diese Direktive entspricht dem Einfügen der gesamten Header-Datei in Ihren Quellcode in derselben Zeile wie die Direktive selbst.
Der Mikrochip XC8-Compiler behandelt .c- und .h-Dateien genau gleich, sodass Sie Ihre Variablendefinitionen in beide einfügen können.
Normalerweise enthält eine Header-Datei "externe" Verweise auf Variablen, die woanders definiert sind (normalerweise eine .c-Datei).
Die Portvariablen mussten an bestimmten Speicheradressen angegeben werden, die der tatsächlichen Hardware entsprechen. Also musste irgendwo eine tatsächliche (nicht externe) Definition existieren.
Ich kann nur vermuten, warum die Microchip Corporation die eigentlichen Definitionen in die .h-Datei geschrieben hat. Eine wahrscheinliche Vermutung ist, dass sie nur eine Datei (.h) anstelle von 2 (.h und .c) wollten (um es dem Benutzer einfacher zu machen).
Wenn Sie jedoch die eigentlichen Variablendefinitionen in eine Header-Datei einfügen und diesen Header dann in mehrere Quelldateien einfügen, beschwert sich der Linker darüber, dass die Variablen mehrfach definiert sind.
Die Lösung besteht darin, die Variablen als statisch zu deklarieren, dann wird jede Definition als lokal für diese Objektdatei behandelt und der Linker wird sich nicht beschweren.
Gommer
Finbarr
JimmyB
static
Globals sind innerhalb der gesamten einzelnen Kompilationseinheit sichtbar und werden nicht darüber hinaus exportiert. Sie sind ähnlich wieprivate
Mitglieder einer Klasse in OOP. Dh jede Variable, die zwischen verschiedenen Funktionen innerhalb einer Kompilationseinheit geteilt werden muss, aber nicht außerhalb dieser cu sichtbar sein soll, sollte es wirklich seinstatic
. Dadurch wird auch das „Clobbering“ des globalen Namensraums des Programms reduziert.Peter Mortensen