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.
1 | uint16_t reboot_counter_ee EEMEM = 0; |
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 | int i = 7; // Definition und Initialisierung |
1 | int i; // 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 | extern int i; |
2 | |
3 | int main() |
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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.