Ein GCC Projekt an dem ich seit paar Wochen bastel bestand bisher nur
aus einer main.h und einer main.c Datei. Inzwischen ist der Quelltext
soweit angewachsen, dass ich dringend auf mehrere .c Dateien aufteilen
muss, um die Übersicht zu behalten.
Bisher waren alle globalen Variablen einfach am Anfang der main.c
definiert. Mit der Aufteilung in mehrere .c Dateien komme ich jetzt in
die Verlegenheit, mich bei den globalen Variablen - bei denen auch paar
EEPROM Variablen dabei sind, sowie paar Variablen die auch in den ISR
verwendet werden - der Thematik "deklariern" vs. "definieren" zu
stellen. Mein Verständnis der Thematik beschränkt sich bisher auf die
einfachen Regeln:
- in der Header Datei wird die globale Variable deklariert, sie ist dann
noch nicht wirklich da, aber alle betroffenen Module wissen dass es
diese Variable irgendwo im Projekt gibt
- in genau nur einer .c Quelltextdatei muss die entsprechende Variable
dann auch tatsächlich definiert werden, um konkreten Speicherplatz dafür
vorzusehen(?)
Jenseits dieses oberflächlichen Wissens fehlen mir derzeit Antworten auf
folgende Detailfragen:
- wenn es bei einer globalen Variablen nicht notwendig ist, diese per
Definition mit einem initialen Wert zu versehen, reicht dann die
Deklaration in der header Datei oder muss die Variable in jedem Fall
auch immer in einer .c Datei definiert werden?
- Wo muss bei einer Variablen die auch in einer ISR manipuliert wird das
volatile hingeschrieben werden - bei der Deklaration, Definition, oder
bei beidem?
- ebenso bei EEPROM Variablen - wo muss das Schlüsselwort EEPROM hin?
Micha schrieb:> Jenseits dieses oberflächlichen Wissens fehlen mir derzeit Antworten auf> folgende Detailfragen:> - wenn es bei einer globalen Variablen nicht notwendig ist, diese per> Definition mit einem initialen Wert zu versehen, reicht dann die> Deklaration in der header Datei oder muss die Variable in jedem Fall> auch immer in einer .c Datei definiert werden?
Gegenfrage: Soll die Variable Speicher zur Verfügung haben, um Werte
abzulegen?
> - Wo muss bei einer Variablen die auch in einer ISR manipuliert wird das> volatile hingeschrieben werden - bei der Deklaration, Definition, oder> bei beidem?
Bei beidem. Es ist dieselbe Variable, also hat sie auch denselben Typ
mit denselben Modifiern.
Micha schrieb:> - wenn es bei einer globalen Variablen nicht notwendig ist, diese per> Definition mit einem initialen Wert zu versehen, reicht dann die> Deklaration in der header Datei oder muss die Variable in jedem Fall> auch immer in einer .c Datei definiert werden?
Die Voraussetzungen die Du der Frage zugrundelegst, sind nicht korrekt:
- Muss eine Variable nur deklariert werden wenn ich sie nicht
initialisiere? Nein. Sie muss in jedem Fall definiert werden.
Das gilt auch für den Spezialfall "Deklaration in einer H-Datei" vs.
"Definition in einer C-Datei". Das sind keine austauschbaren
Alternativen.
- Ist eine Variable definiert, wenn ich sie nur deklariere? Nein. Es
wird kein Speicherplatz angelegt. Spätestens beim Linken gibt es eine
Fehlermeldung.
- Kann man ein Variable initialisieren ohne sie zu definieren? Nein.
Hinweis: Eine H-Datei wird letztlich nur an der Stelle, an der die
#include-Anweisung steht eingefügt.
> - Wo muss bei einer Variablen die auch in einer ISR manipuliert wird das> volatile hingeschrieben werden - bei der Deklaration, Definition, oder> bei beidem?
Ja. Bei beidem. Deklaration und Definition dürfen sich nicht
widersprechen. Andernfalls gibt es eine Fehlermeldung.
> - ebenso bei EEPROM Variablen - wo muss das Schlüsselwort EEPROM hin?
Z.B.
Variablen, die in mehreren Programmdateien, die einzeln kompiliert und
am Schluss gelinkt werden, musst du vor die Deklaration deiner globalen
Variable den Speicherklassenspezifizierer "extern" schreiben und diese
Deklaration dann in dem Programmteil wiederholen, wo die Variable
gebraucht wird:
1.c:
extern char buchstabe='g'
2.c:
extern char buchstabe;
funktion(buchstabe);
Deklarierst du sie in einer Headeratei, erzeugt jede kompilierte
ausführbare Programmdatei, die die Headerdatei inkludiert hat einen
eigenen Speicherbereich für diese Variable: Wird sie in 1.c verändert,
bekommt 2.c nichts davon mit.
volatile und EEPROM gehören in die Deklaration. Bei globalen Variablen
mit Speicherklasse extern kann ich dir aber nicht sagen, obs da in jede
Deklaration in jeder x.c-Datei muss. Ich denke schon, aber wenn man
keine Ahnung hat, einfach mal die Fresse halten.
>... diese per Definition mit einem initialen Wert zu versehen ...
Noch ein Hinweis. Definition und Initialisierung sind begrifflich zwei
verschiedene Dinge. Man initialisiert nicht "per Definition". Eine
Initialisierung kann bei der Definition auftreten. Muss es aber nicht.
1
inti=7;// Definition und Initialisierung
1
inti;// Definition einzeln
2
i=7;// Initialisierung (erste Zuweisung im ganzen Pgm.) einzeln
Danke für die Antworten!
Also muss eine Variable in jedem Fall auch definiert werden, deklariern
reicht nicht aus. Der GCC meckert nämlich leider nicht, wenn man nur
deklariert aber das definieren vergisst. Aber dass C eine Sprache ohne
Sicherheitsgurt und Hosenträger ist, ist ja ein alter Hut ;-)
Bin grade mitten in dem Prozess, die Definition der globalen Variablen
in die diversen .c Texte auseinanderzusortieren, was sich irgendwie
nicht "richtig" anfühlt. Eventuell ist es eine gute Idee, dem Projekt
eine zusätzliche .c Datei zu spendieren - Name z.B. globalvars.c - in
der lediglich alle globalen Variablen definiert+initialisiert werden?
Ist möglich. Du kennst dein Projekt selbst am besten.
Mit dem Flag -Wall wird der gcc sehr gesprächig und nörgelt auch beim
Lesen von uninitialisierten Variablen.
>Der GCC meckert nämlich leider nicht, wenn man nur deklariert aber das >definieren vergisst.
Ganz streng gesehen, hast Du recht. Aber es wundert mich doch ein wenig,
dass Du sowas schreibst. Zwar ist es der Linker der dann eine
Fehlermeldung ausgibt, aber es gibt eine Fehlermeldung. Der findet
nämlich keine Speicherplatz dem er Zugriffe auf die Variable zuordnen
könnte.
>... was sich irgendwie nicht "richtig" anfühlt.
Schau zum Vergleich mal in den Thread:
Beitrag "FX3: C-Mehrfachdefinition / Include-Issue"
Micha schrieb:> Der GCC meckert nämlich leider nicht, wenn man nur> deklariert aber das definieren vergisst. Aber dass C eine Sprache ohne> Sicherheitsgurt und Hosenträger ist, ist ja ein alter Hut ;-)
Das ist halt eine alte Computersprache aus den frühen siebziger Jahren
des vorigen Jahrhunderts, einer Zeit in der es noch lange keine PCs gab
und der Massenspeicher für Programme aus bunten Papierstreifen mit
Lochkodierung bestand. Als Rechenspeicher verwendet man damals oft noch
auf Kupferdrähte aufgefädelte Ferritringe. Was willst du da an Komfort
erwarten.
@ Werner M.
>Was willst du da an Komfort erwarten.
Du zählst ja auch mit ca. 2,5 Mio. Jahren alten "Fingern" ( Homo
rudolfensis und Homo habilis), bist aber über korrekte Resultate dennoch
nicht überrascht.
Ich glaube, mehr muss man zu Deinem Beitrag nicht schreiben.
Micha schrieb:> Der GCC meckert nämlich leider nicht, wenn man nur> deklariert aber das definieren vergisst.
Selbstverständlich meckert er. Es kommt dann eine "undefined reference"
Meldung beim Linken.
Micha schrieb:> Bisher waren alle globalen Variablen einfach am Anfang der main.c> definiert.
Ähm. Wozu brauchst Du bei einer einzigen Quellcode-Datei denn globale
Variablen? Gut, Du teilst jetzt auf mehrere Dateien auf. Aber selbst
dann kommt man oftmals ohne aus, und hat am Ende besseren Code.
Der Begriffe Deklaration und Definition kommen aus der Politik.
Eine Deklaration ist auch dort eine Ansammlung von Begriffen die aber
keinerlei Wirkung erzielen.
Eine Definition ist dort aber meist das (gewollte oder ungewollte)
Gegenteil der Deklaration.
Beispiel:
Deklarationen: EU Verträge, Maastrich Abkommen, Vertrag von Lissabon
Daraus folgende Definitionen: Korruption, Finanzkrise, Arbeitslosigkeit
Die auch in C auftretenden Seiteneffekte sind auch dort vorhersagbar:
EUdssR, Beamtenstaat, Staatskonkurs, Abbau der Bürgerrechte.
Bitflüsterer schrieb:> Noch ein Hinweis. Definition und Initialisierung sind begrifflich zwei> verschiedene Dinge. Man initialisiert nicht "per Definition". Eine> Initialisierung kann bei der Definition auftreten. Muss es aber nicht.
Der Vollständigkeit halber: Globale Variablen werden, wenn man nicht
explizit eine Initialisierung hinschreibt, implizit mit 0 initialisiert.
> int i = 7; // Definition und Initialisierung> int i; // Definition einzeln> i = 7; // Initialisierung (erste Zuweisung im ganzen Pgm.) einzeln
Eine Zuweisung ist keine Initialisierung, auch wenn in C der effektive
Unterschid nicht groß ist.
Micha schrieb:> Also muss eine Variable in jedem Fall auch definiert werden, deklariern> reicht nicht aus. Der GCC meckert nämlich leider nicht, wenn man nur> deklariert aber das definieren vergisst. Aber dass C eine Sprache ohne> Sicherheitsgurt und Hosenträger ist, ist ja ein alter Hut ;-)
1
externinti;
2
3
intmain()
4
{
5
i=3;
6
}
1
$ gcc extern.c
2
extern.c:(.text+0x6): undefined reference to `i'
3
collect2: Fehler: ld gab 1 als Ende-Status zurück
> Bin grade mitten in dem Prozess, die Definition der globalen Variablen> in die diversen .c Texte auseinanderzusortieren, was sich irgendwie> nicht "richtig" anfühlt. Eventuell ist es eine gute Idee, dem Projekt> eine zusätzliche .c Datei zu spendieren - Name z.B. globalvars.c - in> der lediglich alle globalen Variablen definiert+initialisiert werden?
Kann man machen, aber meist teilt man sein Programm über die C-Files ja
in Module mit getrennten Aufgaben auf. Die Variable gehört in der Regel
zu einem bestimmten Modul, und da gehört dann auch die Definition hin.
Im dazugehörigen Header steht dann die Deklaration.
@ Rolf Magnus
>Der Vollständigkeit halber: Globale Variablen werden, wenn man nicht>explizit eine Initialisierung hinschreibt, implizit mit 0 initialisiert.
Das sollte man wissen. Danke für die Ergänzung.
> int i = 7; // Definition und Initialisierung> int i; // Definition einzeln> i = 7; // Initialisierung (erste Zuweisung im ganzen Pgm.) einzeln>Eine Zuweisung ist keine Initialisierung, auch wenn in C der effektive>Unterschid nicht groß ist.
Das ist an sich richtig, wenn wir von der C-Syntax reden in deren
Definition die "Initialisierung" eine syntaktische Kategorie, ein
Non-Terminal ist. Diese Initialisierung kann im Programmtext nur
unmittelbar bei der Definition auftreten, wie es in der ersten Zeile
oben gezeigt ist, oder garnicht.
Aber in dem weiteren Kontext der Programmierung wird die erstmalige
Zuweisung an eine Variable auch "Initialisierung" genannt.
Deswegen auch in der dritten Zeile, in der Klammer, der ergänzende
Hinweis.
Zugegeben, das hätte ich detaillierter beschreiben können, wobei aber
mein Gedanke war, das diese Unterschiede für einen Anfänger doch eher -
äh - esoterisch sind.
Naja, vielleicht bin ich da auch nur etwas durch C++ vorbelastet. Da ist
es schon wesentlich wichtiger, zwischen Initialisierung und Zuweisung zu
unterscheiden.
Wobei es auch in C wichtig sein kann, z.B. bei const.
Vielen Dank für alle Antworten!
Ich versuch mal eine Zusammenfassung:
Bei einem kleinen C-Projekt, das nur eine .c Datei enthält, kann man den
ganzen Aufklapp vergessen. Globale Variablen werden einfach am Anfang
des Quelltexts definiert und ggf. im gleichen Aufwasch mit initialen
Werten belegt.
Wenn ein anfangs einfaches C Projekt im Laufe der weiteren Entwicklung
"aus den Nähten quillt" kommt irgendwann der Moment, wo der Quelltext
besser auf mehrere Dateien aufgeteilt wird, z.B. sortiert nach
Aufgabengebiet. An dieser Stelle ist dann das Wissen gefragt, wie man
die Aufteilung "richtig" bewerkstelligt. Insbesondere bei globalen
Variablen ist die Frage von Interesse, wie man diese "richtig" für alle
Beteiligten zugänglich macht.
Soweit ich es derzeit verstehe, können Variablen garnicht, einmal oder
beliebig oft DEKLARIERT werden. Eine DEKLARATION findet in einer
Header-Datei statt und bedeutet nicht mehr als die Erklärung: irgendwo
im Projekt existiert eine Variable die diesen Namen trägt. Ausserdem
sollten bei der Deklaration die Attribute volatile oder EEPROM mit
aufgeführt werden, passend zu der (späteren und konkreteren) Definition.
Bei einer Deklaration darf NIE ein initialer Wert zugewiesen werden.
Eine Deklaration reserviert keinen konkreten Speicher, sondern macht
eine Variable nur im Projekt bekannt. Daher MUSS jede deklarierte
Variable zusätzlich auch DEFINIERT werden. Und zwar genau EIN MAL im
Projekt. Der Definition kann in C direkt eine initiale Zuweisung folgen.
Soweit ich es derzeit verstehe ist der "extern" qualifier bei der
Deklaration immer sinnvoll, wenn eine globale Variable womöglich in
mehreren .c Modulen verwendet wird. Also im Zweifelsfall ist es immer
eine gute Idee das "extern" vor alle Variablen-Deklarationen in einer
Header-Datei zu schreiben.
Micha schrieb:> Soweit ich es derzeit verstehe, können Variablen garnicht, einmal oder> beliebig oft DEKLARIERT werden.
Naja, wenn sie definiert ist, ist sie auch zumindest einmal deklariert,
denn jede Definition ist automatisch auch eine Deklaration.
> Soweit ich es derzeit verstehe ist der "extern" qualifier bei der> Deklaration immer sinnvoll, wenn eine globale Variable womöglich in> mehreren .c Modulen verwendet wird. Also im Zweifelsfall ist es immer> eine gute Idee das "extern" vor alle Variablen-Deklarationen in einer> Header-Datei zu schreiben.
Wenn du das "extern" nicht davor schreibst, ist es eine Definition.
Daher ist es nicht nur eine gute Idee, sondern zwingend erforderlich,
wenn du die Variable nur deklarieren willst.
Rolf Magnus schrieb:> Der Vollständigkeit halber: Globale Variablen werden, wenn man nicht> explizit eine Initialisierung hinschreibt, implizit mit 0 initialisiert.
Das ist Sache irgendwelcher Startupcodes, genauso wie die Vorbelegung
von Variablen mit irgendwelchen Initialwerten. Am PC ist sowas nett und
recht üblich, aber auf einem µC sollte man sich auf SOWAS nie und nimmer
verlassen. Da gilt immer "was man nicht dediziert initialisiert hat,
sollte man als uninitialisiert auffassen". Und das Initialisieren
erfolgt im Code als Zuweisung:
Also
int A;
...
A = 4711;
und nicht per
int A = 4711;
denn letzteres bewirkt nur, daß A in einem RAM-Bereich angeordnet wird,
der vom Startupcode dadurch initialisiert wird, daß er einen vom Linker
vorbereiteten Codebereich dorthin kopiert - WENN der Startupcode sowas
überhaupt macht. Kann sein, aber verlassen würde ich mich NIE und NIMMER
darauf. Besonders dann nicht, wenn es im verwendeten µC unterschiedliche
Restartmechanismen gibt wie z.B. Kaltstart und Warmstart oder so.
W.S.
W.S. schrieb:> Rolf Magnus schrieb:>> Der Vollständigkeit halber: Globale Variablen werden, wenn man nicht>> explizit eine Initialisierung hinschreibt, implizit mit 0 initialisiert.>> Das ist Sache irgendwelcher Startupcodes, genauso wie die Vorbelegung> von Variablen mit irgendwelchen Initialwerten. Am PC ist sowas nett und> recht üblich, aber auf einem µC sollte man sich auf SOWAS nie und nimmer> verlassen.
Ein ISO-C-konformer Compiler muss alle globalen Variablen
initialisieren. Allerdings gibt's im µC-Bereich Compiler, die in dieser
Hinsicht nicht konform sind. GCC ist es normalerweise, wenn der
Startup-Code nicht gerade verhunzt wurde.
> denn letzteres bewirkt nur, daß A in einem RAM-Bereich angeordnet wird,> der vom Startupcode dadurch initialisiert wird, daß er einen vom Linker> vorbereiteten Codebereich dorthin kopiert - WENN der Startupcode sowas> überhaupt macht.
Auch hier ist, wenn das nicht funktioniert, die Toolchain vermurkst.
Kann aber natürlich durchaus sein. Gerade bei µC-Compilern gibt's viel
Schrott und halbfertige Linker-Skripte, durch die das nicht so
funktioniert, wie es eigentlich sollte.
W.S. schrieb:> WENN der Startupcode sowas überhaupt macht. Kann sein, aber verlassen> würde ich mich NIE und NIMMER darauf.
Wenn du davon ausgehst, dass der Startupcode fehlerhaft ist, solltest du
konsequenterweise davon ausgehen, dass auch der Compiler, der Assembler,
der Linker und die Standardbibliothek fehlerhaft sind. Es bleibt dir
also nichts anderes übrig, als den handgeschriebenen Maschinencode
deines Programms Bit für Bit per Mikroskop und spitzen Elektroden in die
einzelnen Flash-Zellen des zuvor geöffneten Mikrocontrollers zu
injizieren.
Ob das Ergebnis dieser Vorgehensweise am Ende tatsächlich zuverlässiger
funktioniert, möchte ich mal ganz arg dahingestellt lassen ;-)
W.S. schrieb:> Rolf Magnus schrieb:>> Der Vollständigkeit halber: Globale Variablen werden, wenn man nicht>> explizit eine Initialisierung hinschreibt, implizit mit 0 initialisiert.>> Das ist Sache irgendwelcher Startupcodes, genauso wie die Vorbelegung> von Variablen mit irgendwelchen Initialwerten. Am PC ist sowas nett und> recht üblich
Nö, es ist nicht 'üblich'.
Es ist notwendig, damit sich das Paket aus Compiler und Runtime-System
überhaupt ein 'C' Paket nennen darf.
Mag sein, dass einige sogenannte C-Compiler auf einem embedded System
sich nicht daran halten. Aber dann dürften sie sich konsequenterweise
nicht C-Compiler nennen.
Es gibt nun mal gewisse Grunddinge, von denen muss man ausgehen, wenn
man einen Compiler benutzt. Und auch wenn die C-Standardisierung so
manches offen lässt, ein paar Dinge sind dann doch klipp und klar
geregelt. Andernfalls darf sich so ein Compiler dann eben nicht
C-Compiler nennen. Wobei es natürlich auch gewisse Abstufungen gibt,
welche 'Abweichungen' in der Praxis gezwungenermassen toleriert werden
und welche nicht bzw. welche Verhaltensweisen compilerspezifisch mittels
Optionen ein/aus schaltbar sind. Ob ein Runtime System die letzten
Features und Feinheiten im Format-String von printf beherrscht oder
nicht, wird wesentlich leichter zu verschmerzen sein, als wenn ein
System die Overflow-Behandlung in der unsigned Rechnerei nicht korrekt
hinkriegt. Initialisierungen zu vermurksen fällt in letzteren Bereich.
Wenn ich bei Audi ein Auto kaufe, muss ich auch nicht extra darauf
hinweisen, dass ich Räder haben will und der Verkäufer hat schlechte
Karten, wenn dann die Karre vor mir aufgebockt zur Abholung bereit
steht.
Insbesondere im Embedded-Bereich ist die Initialisierung globaler
Variablen durch Startup-Codes sehr sinnvoll (und daher auch von
Compilern unterstützt) da typischerweise viel effizienter als die
manuelle Initialisierung jeder einzelnen Variable im Code. Das
resultiert daraus dass der Startupcode die Initialisierung
typischerweise mit einer Art memcpy (Flash->RAM) macht, was schneller
und Codegröße-technisch kleiner ist als eine Zuweisung pro Variable.
Besonders schlimm wäre die fehlende Initialisierungsmöglichkeit bei
Arrays. Man stelle sich ein Byte-Array mit etwa 1000 vorbelegten
Elementen vor. Würde man die Werte nicht direkt initialisieren, sondern
in einer C-Funktion den Array-Elementen zuweisen, würde das auf einem
AVR 6 kB statt 1 kB im Flash belegen. Nein, das will wirklich niemand.
@W.S.: Welche C-Toolchain kennst du, die von dem Problem betroffen ist?
Ich kann mir nicht vorstellen, dass irgendeine aktuelle C-Toolchain die
Variableninitialisungen per Default unterbindet. Entweder es handelt
sich dabei um einen Bug einer älteren Version, der mittlerweile behoben
ist, oder die Initialisierung lässt sich optional ausschalten, was dann
aber in der Verantwortung des Programmierers liegt.
Natürlich kann man jederzeit den Startupcode mutwilligerweise durch
seinen eigenen ersetzen. Genauso gut kann man aber auch die
Standardbibliothek, die ebenfalls Bestandteil des Laufzeitsystems ist,
verhunzen. Das kann man dann aber nicht dem Toolchain-Hersteller
ankreiden.