Forum: Compiler & IDEs C, ein paar Detailfragen zum Thema deklariern vs. definieren


von Micha (Gast)


Lesenswert?

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?

von Stefan R. (srand)


Lesenswert?

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.

von Bitflüsterer (Gast)


Lesenswert?

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;

von Joschua C. (Gast)


Lesenswert?

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.

von Bitflüsterer (Gast)


Lesenswert?

>... 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

von Micha (Gast)


Lesenswert?

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?

von Joschua C. (Gast)


Lesenswert?

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.

von Bitflüsterer (Gast)


Lesenswert?

>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"

von Werner M. (Gast)


Lesenswert?

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.

von Bitflüsterer (Gast)


Lesenswert?

@ 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.

von Mark B. (markbrandis)


Lesenswert?

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.

von Der Rächer der Transistormorde (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von Bitflüsterer (Gast)


Lesenswert?

@  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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von Micha (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
Noch kein Account? Hier anmelden.