Hallo zusammen,
ich versuche gerade mir die Programmiersprache C von Grundauf
anzueignen.
Leider komme ich immer wieder an Punkte an denen ich mir nicht wirklich
erklären kann was ich da gerade programmiere.
So mag mein Code zwar richtig sein und führt zum korrekten Ergebnis.
Trotzdem ist es für mich unbefriedigend, weil mir nicht klar ist WIESO
mein Code richtig ist.
Ein gutes Beispiel ist die Speicherklasse extern. So weiß ich, dass
extern nur für globale Variablen gilt und nur in der Header-Datei
benutzt wird.
Auch der Unterschied zwischen einer Deklaration und einer Definition ist
mir bewusst. Leider weiß ich nicht - natürlich nur bei Variablen - wie
der Compiler jetzt die Deklaration und die Definition voneinander
unterscheidet.
Das ist wichtig für mich um zu verstehen wie extern dann auch
letztendlich wirksam wird.
Ein Beispiel:
-> es gibt drei Dateien: main.c, erste_datei.h, erste_datei.c. Die
Header-Datei wird via #include in die anderen beiden Dateien
eingebunden.
Die Speicherklasse extern bewirkt, dass eine globale Variable auch
wirklich nur einmal definiert wird. Der Rest ist dann für den Compiler
eine Deklaration derselben Variable. Selbst wenn der Programmierer das
selber nicht so gedacht hatte.
Das ganze funktioniert beim GCC nämlich auch ohne das "extern" vor der
Deklaration.
Hier sind die erwähnten Dateien:
printf("Die zweite Variable ist %d.\n",zweiteVariable);
13
printf("Die erste Variable ist %d.\n",ersteVariable);
14
return0;
15
}
1
/*erste_Datei.h*/
2
3
#ifndef ERSTE_DATEI_H
4
#define ERSTE_DATEI_H
5
6
externintersteVariable;/*funktioniert auch prima ohne "extern"*/
7
8
externvoidhello();
9
externintinitialisiereVariable(int);
10
11
#endif
1
/*erste_datei.c*/
2
3
#include"erste_datei.h"
4
#include<stdio.h>
5
6
intersteVariable;/*Fehler bei Initialisierung an dieser Stelle*//*<- 2*/
7
8
voidhello()
9
{
10
printf("Hello World!\n");
11
}
12
13
intinitialisiereVariable(intparam)
14
{
15
param=100212;
16
returnparam;
17
}
Ich frage mich also nach welchen Kriterien der Compiler nun entscheidet,
welche Definition (1 oder 2) er nun auch wirklich als Definition zulässt
und welche er in eine Deklaration umwandelt.
Diese Frage bezieht sich sowohl auf die Definition mit Initialisierung
und jene ohne eine Initialisierung.
Wiese dürfen nicht beide Definitionen eine Initialisierung enthalten?
Ich glaube die Antwort hängt auch damit zusammen in welcher Reihenfolge
der Kompiler die Quelltexte bearbeitet.
Da ich darüber aber nur über bruchstückhaftes Wissen verfüge hilft mir
das auch nur bedingt und mit etwaigen Hilfestellungen weiter.
Kann mir eventuell jemand hier im Forum weiterhelfen?
Mit freundlichen Grüßen
Pallas
Wenn du es genauer verstehen willst, dann solltest du dich in den Ablauf
im Bildprozess einarbeiten. Also was macht der Compiler mit welchen
Dateien und was macht der Linker.
Der Unterschied ist die Deklaration und Definition. Mit extern
deklariert du eine Variable und sagst dem Compiler, dass es später eine
Variable mit dem Namen und Typ geben wird. In einer C Datei muss dann
die Variable definiert werden, damit diese beim Linken vorhanden ist. Es
gilt außerdem, dass jede Variable nur einmal definiert werden darf. Wenn
du natürlich nur eine C Datei hast, dann fällt das nicht auf, ob extern
richtig gesetzt ist.
Daniel S. schrieb:> Ich frage mich also nach welchen Kriterien der Compiler nun entscheidet,> welche Definition (1 oder 2) er nun auch wirklich als Definition zulässt> und welche er in eine Deklaration umwandelt.
Er entscheidet das garnicht, denn diese Entscheidung hast du ihm ja
gerade abgenommen: Mit extern ist es eine Deklaration, ohne ist es
eine Definition.
Dem Compiler ist das überdies auch völlig egal, denn er verteilt ja
keinen Speicher. Das macht nämlich der Linker: Im Prinzip reserviert er
für jede Definition (also ohne extern) ein Plätzchen im Speicher und
vergibt diesem Plätzchen den Variablennamen. Und für jede Deklaration
(mit /extern) schaut er nach, ob er irgendwo ein passendes Plätzchen mit
dem Variablennamen hat.
Dabei gibts zwei Fälle: Jemand hat eine Deklaration verwendet und er
findet kein Plätzchen. Das gibt diese Linker-Fehler "unresolved symbol"
oder "undefined symbol" oder so ähnlich. Oder aber er möchte ein neues
Plätzchen mit einem Namen reservieren, aber es gibt schon ein Plätzchen
mit dem Namen.
Das, was du jetzt gerade beobachtest, nennt sich *tentative
declaration*. Das ist quasi eine Definition ohne Initialisierung.
Solange eine Definition keine Initialisierung hat (also eine tentative
declaration ist), kannst du die beliebig oft hinschreiben. Der Linker
behandelt sie dann so, als ob sie mit extern vereinbart wurde.
Andernfalls darf es genau eine Initialisierung geben, wie du ja
beobachtet hast, die dann auch verwendet wird.
Ist m.M.n. etwas blöd in C (und wurde in C++ zu einem Fehler geändert).
Auf globaler (Datei-)Ebene sollte man alle Variablen static machen...
In den Kompiliervorgang möchte und werde ich mich noch tiefer
einarbeiten.
Mich verwirrt nur, dass auch dieser Quellcode problemlos funktioniert:
1
/*erste_Datei.h*/
2
3
#ifndef ERSTE_DATEI_H
4
#define ERSTE_DATEI_H
5
6
intersteVariable;/*funktioniert auch prima ohne "extern"*/
7
8
externvoidhello();
9
externintinitialisiereVariable(int);
10
11
#endif
Wieso ist das so?
Kann doch eigentlich gar nicht sein?
Und wenn es sich dabei um eine reine Deklaration handelt. Was passiert
dann, wenn ich den Header in eine Datei einbinde, die noch gar keine
globale Variable gleichen Typ und Namens definiert hat?
Das kann ja auch vorkommen.
Also mir hat man das so erklärt, dass die Deklaration nur sagt, in einer
Datei, die eben diesen Header inkludiert wird eine solche Variable
definiert. Deswegen muss die Variable ja auch unbedingt in der
erste_datei.c definiert werden. Es reicht nicht aus sie einfach zu
benutzen.
Im obigen Beispiel könnte man schließlich anführen, es würde doch
reichen dem Compiler mitzuteilen das es die Variable gibt.
Da es aber, wie beschrieben, auch vorkommen kann das der Header in eine
Datei eingebunden wird, welche die abesagte Variable nie definiert hat,
muss sie unbedingt in der erste_datei.c definiert werden. So ist
sichergestellt, dass wenigstens eine Definition vorliegt.
So weit glaube ich also verstanden zu haben.
Ich verstehe also was der Unterschied zwischen dem deklarieren und dem
definieren ist und wieso wenigstens in der erste_datei.c eine Definition
erfolgen muss.
Was ist nicht verstehe ist folgendes:
-> Wieso funktioniert es in der erste_datei.h auch OHNE "extern"?
Hängt der Compiler das vielleicht standardmäßig davor? Er weiß ja, dass
es in .h-Dateien sowieso nur Deklarationen gibt.
-> Wieso darf ich nicht in allen Dateien bei der Definition eine
Initialisierung ausführen?
Ich vermute das der Compiler an eben dieser Initialisierung festmacht,
aus welcher Datei die Varaiblendefinition nun auch wirklich als
Definition ausgeführt wird.
Besteht als in Datei A eine Definition MIT Initialisierung und in Datei
B eine OHNE Initialisierung, wird die Definition in Datei B gestrichen.
Aber das ist nur eine Vermutung.
-> Wie verfährt der Compiler bei der Wahl der Definition, wenn es gar
keine Initialisierung gibt?
Meine Vermutung ist, dass er die Datei welche vor dem Linken entsteht
von oben nach unten durchsucht und die erste Definition nimmt. Alle
anderen streicht er.
Lieben Gruß
Pallas
... um zur weiteren Verwirrung beizutragen (bzw. ein tieferes
Verständnis zu bekommen, was da eigentlich passiert), solltest Du dir
auch mal die Compiler-Optionen -fcommon bzw. -fno-common anschauen.
Sven P. schrieb:> Daniel S. schrieb:>> Ich frage mich also nach welchen Kriterien der Compiler nun entscheidet,>> welche Definition (1 oder 2) er nun auch wirklich als Definition zulässt>> und welche er in eine Deklaration umwandelt.> Er entscheidet das garnicht, denn diese Entscheidung hast du ihm ja> gerade abgenommen: Mit extern ist es eine Deklaration, ohne ist es> eine Definition.
Das stimmt schon. Und für mich bedeutet das, dass die Deklaration in
erste_datei. eben auch wirklich eine Deklaration ist. Wofür braucht der
Compiler dann die Definition in erste_datei.c. Oder ist das gar keine
Definition weil in der Header-Datei das "extern" steht.
Meine Frage also: steht die Deklaration in der Header-Datei oder steht
dort nur der, ich sag jetzt mal "Hinweis" auf die Existenz einer
Deklaration in der zugehörigen .c-Datei.
Eben so wie bei Funktionen.
Ich dachte eben dieser "Hinweis" IST eine Deklaration.
Sven P. schrieb:> Jemand hat eine Deklaration verwendet und er> findet kein Plätzchen. Das gibt diese Linker-Fehler "unresolved symbol"> oder "undefined symbol" oder so ähnlich.
In meinem Beispiel ist das offensichtlich nicht ganz zutreffend. Selbst
wenn ich die Definition in der main.c weglasse, läuft der Compiler
problemlos durch.
Daniel S. schrieb:> Und wenn es sich dabei um eine reine Deklaration handelt. Was passiert> dann, wenn ich den Header in eine Datei einbinde, die noch gar keine> globale Variable gleichen Typ und Namens definiert hat?
Garnichts. Der Compiler baut eine Variable mit dem Variablennamen ein
und überlässt es dem Linker, anstelle des Variablennamens ein Plätzchen
im Speicher einzusetzen.
Das kann man sogar recht wörtlich so verstehen: Der Compiler erzeugt ein
unaufgelöstes Symbol in seiner Ausgabe (also im Maschinencode, wenn
man so will). Der Linker muss dieses Symbol dann auflösen, indem er es
durch eine konkrete Speicheradresse ersetzt.
Darum heißt er ja auch Linker (oder früher: Binder). Weil er solche
unaufgelösten Symbole aus mehreren Stücken Maschinencode (quasi pro
.c-Datei ein Stück) aufsammelt und zusammenbindet.
> Das kann ja auch vorkommen.
Das ist der Normalfall.
> Also mir hat man das so erklärt, dass die Deklaration nur sagt, in einer> Datei, die eben diesen Header inkludiert wird eine solche Variable> definiert. Deswegen muss die Variable ja auch unbedingt in der> erste_datei.c definiert werden. Es reicht nicht aus sie einfach zu> benutzen.
Korrekt. Mit der Definition erzeugst du ein echtes Symbol, mit dem der
Linker die ganzen unaufgelösten Symbole verknoten kann.
> Im obigen Beispiel könnte man schließlich anführen, es würde doch> reichen dem Compiler mitzuteilen das es die Variable gibt.
Genau das tust du ja mit der /extern/-Deklaration.
> Da es aber, wie beschrieben, auch vorkommen kann das der Header in eine> Datei eingebunden wird, welche die abesagte Variable nie definiert hat,> muss sie unbedingt in der erste_datei.c definiert werden. So ist> sichergestellt, dass wenigstens eine Definition vorliegt.
Ja. Und in der Regel will man, dass es genau eine Definition gibt.
Darum sind diese tentative Deklarationen eigentlich blöd.
Man kann das -fno-common einschalten, was Markus vorschlägt. Dann wird
das Verhalten etwas intuitiver.
> Wieso funktioniert es in der erste_datei.h auch OHNE "extern"?> Hängt der Compiler das vielleicht standardmäßig davor? Er weiß ja, dass> es in .h-Dateien sowieso nur Deklarationen gibt.
Nein, weiß er nicht. Genaugenommen ist ihm die Dateiendung (.h) auch
ziemlich egal. Er sieht davon ja auch nix mehr, denn noch bevor der
Compiler deinen Quelltext sieht, wurden alle #include-Zeilen einfach
durch den Inhalt der entsprechenden Dateien ersetzt (vom Präprozessor).
> -> Wieso darf ich nicht in allen Dateien bei der Definition eine> Initialisierung ausführen?
Naja, weil es sich beißt. Welche soll denn letztlich zählen?
> Ich vermute das der Compiler an eben dieser Initialisierung festmacht,> aus welcher Datei die Varaiblendefinition nun auch wirklich als> Definition ausgeführt wird.
Ja, solange alle anderen Definitionen tentativ sind. Durch die
Initialisierung wird die quasi festgenagelt und es darf ansonsten keine
weitere mit Initialisierung mehr geben. Wie gesagt, macht man
normalerweise nicht mehr.
Daniel S. schrieb:> -> Wie verfährt der Compiler bei der Wahl der Definition, wenn es gar> keine Initialisierung gibt?
Laut C-Standard werden die mit 0 initialisiert.
> Meine Vermutung ist, dass er die Datei welche vor dem Linken entsteht> von oben nach unten durchsucht und die erste Definition nimmt. Alle> anderen streicht er.
Ja. Darum heißen sie auch tentativ (englisch: vorläufig). Solange die
alle gleich lauten, macht der Linker nachher ein einziges Plätzchen im
Speicher draus. Wenn eine davon eine Initialisierung hat, dann zählt die
und weitere mit Initialisierung führen zum Fehler. Wenn sie alle extern
sind, gibt es kein Symbol und auch einen Fehler. Wenn eine static ist,
dann sieht der Linker sie garnicht.
Daniel S. schrieb:> Sven P. schrieb:>> Jemand hat eine Deklaration verwendet und er>> findet kein Plätzchen. Das gibt diese Linker-Fehler "unresolved symbol">> oder "undefined symbol" oder so ähnlich.>> In meinem Beispiel ist das offensichtlich nicht ganz zutreffend. Selbst> wenn ich die Definition in der main.c weglasse, läuft der Compiler> problemlos durch.
Ja klar, dann zieht halt die Definition in erste_datei.c.
Wow, das ist ja grausam für einen Anfänger. :-)
Es gibt also sogar noch eine Art Zwischenstufe zwischen Definition und
Deklaration. -> vorläufige Deklarationen
Jetzt muss ich mich mal konzentrieren.
Im Beispiel welche ich im ersten Post beschrieben habe ist also die
Variable ersteVariable aus der Datei main.c die Definition.
Und zwar aus mehreren Gründen:
-> einmal wegen dem "extern" in erste_datei.h. Dieses markiert die
Definition in erste_datei.c erst einmal als Deklaration?
Oder markiert es die entsprechende Zeile in der Header-Datei selbst als
Deklaration?
-> und selbst ohne das "extern" im Header handelt es sich in
erste_datei.h um eine vorläufige Definition. Diese wird so oder so von
der Definition mit Initialisierung überschrieben.
Daniel S. schrieb:> Es gibt also sogar noch eine Art Zwischenstufe zwischen Definition und> Deklaration. -> vorläufige Deklarationen
Ja, aber vergiss die am besten wieder, die ist von historischer
Bedeutung.
Darum verweise ich ja auf das -fno-common-Flag: Damit entfällt diese
Zwischenstufe und es werden echte Definitionen draus, mit entsprechendem
Fehler, wenn sie mehrfach auftauchen.
> Jetzt muss ich mich mal konzentrieren.> Im Beispiel welche ich im ersten Post beschrieben habe ist also die> Variable ersteVariable aus der Datei main.c die Definition.
Es ist erstmal eine Definition von möglicherweise vielen.
Aber es ist eine mit Initialisierung.
> Und zwar aus mehreren Gründen:> -> einmal wegen dem "extern" in erste_datei.h. Dieses markiert die> Definition in erste_datei.c erst einmal als Deklaration?> Oder markiert es die entsprechende Zeile in der Header-Datei selbst als> Deklaration?Nur diese Zeile in der Header-Datei markiert sie als Deklaration,
denn nur dort steht das extern.
> -> und selbst ohne das "extern" im Header handelt es sich in> erste_datei.h um eine vorläufige Definition. Diese wird so oder so von> der Definition mit Initialisierung überschrieben.
Nur ohne das extern handelt es sich darum.
Ich würds so zusammenfassen, wobei ich das mit der vorläufigen
Definition erstmal unterschlage:
- Es ist völlig egal, was im Header und was im Source steht. Der
Compiler sieht eh nix von dieser Unterscheidung.
- Wenn irgendwo eine Variable mit extern steht, dann hat sie externe
Speicherklasse, ist also eine Deklaration.
- Wenn irgendwo eine Variable ohne extern steht, dann ist das eine
Definition.
Deklarationen kann man beliebig kopieren. Das macht man ja etwa durch
das #include vom Header. Dadurch landet die Deklaration (mit extern) in
jedem Durchlauf. Und man kann die damit deklarierte Variable verwenden,
wodurch ein unaufgelöstes Symbol entsteht.
Eine Deklarierte Variable muss irgendwo einmalig definiert werden,
damit sie auch im Speicher platziert wird. Typischerweise in einer
Source-Datei. Es ist egal, ob die Variable dabei initialisiert wird oder
nicht. Dadurch entsteht ein echtes Symbol.
Bim Linken werden die unaufgelösten Symbole dann mit dem einen echten
Symbol verknotet.
Bis hierhin ist jetzt alles wasserdicht, denke ich. In der main.c wird
also ein unaufgelöstes Symbol erzeugt (durch das extern im Header) und
gleich darauf eine passende Definition. In der anderen Source wird nur
das unaufgelöste Symbol erzeugt.
Das mit der vorläufigen Definition heißt "common section". Dabei erzeugt
der Compiler möglicherweise mehrere gleichnamige echte Symbole, weil
es ja an mehreren Stellen (tentative) Definitionen gibt. Der Linker
sammelt diese dann auf und verstaut sie einmalig an dieselbe
Speicherstelle.
Das ist nicht ganz risikolos, denn möglicherweise definiert irgendeine
andere Source-Datei eine Variable von der du garnichts weißt, und die
genauso heißt wie eine von dir. Dann fasst der Linker die zusammen und
du wunderst dich...
Sven P. schrieb:>> Und zwar aus mehreren Gründen:>> -> einmal wegen dem "extern" in erste_datei.h. Dieses markiert die>> Definition in erste_datei.c erst einmal als Deklaration?>> Oder markiert es die entsprechende Zeile in der Header-Datei selbst als>> Deklaration?> Nur diese Zeile in der Header-Datei markiert sie als Deklaration,> denn nur dort steht das extern.
Ich glaube ich verstehe nicht ganz was die Zeile mit dem "extern" in der
Header-Datei darstellen soll.
Auf alle drei Dateien bezogen:
Wie viele Definitionen / Deklarationen gibt es?
-> gibt es drei verteilt auf alle drei Dateien
-> oder gibt es nur zwei, verteilt auf die Dateien mit der Endung .c
Letzten Endes möchte ich also wissen ob die Einträge in der Header-datei
"nur" Verweise darstellen oder ob sie selbst Definitionen /
Deklarationen sind.
Daniel S. schrieb:> Ein gutes Beispiel ist die Speicherklasse extern. So weiß ich, dass> extern nur für globale Variablen gilt und nur in der Header-Datei> benutzt wird.
Speicherklasse? Nö, sowas gibt's hier nicht. Das Wort 'extern' zeigt dem
Compiler lediglich an, daß dasjenige, was dahinter kommt, eben nicht
in der gerade vorliegenden Quelle vorhanden ist, sondern irgendwo anders
und darum kümmert sich dann nicht der Compiler, sondern der Linker.
Das ist alles.
Bei externen Variablen muß man das extern in der .h davorschreiben, da
diese ja auch in andere Quellen inkludiert wird.
Und man kann in der .h das extern auch vor Funktionen schreiben - der
Einheitlichkeit zuliebe, aber man braucht das in diesem Falle
ausnahmsweise mal nicht.
W.S.
Daniel S. schrieb:> Die Speicherklasse extern
Die Speicherklasse nennt sich "Static Storage".
Üblicherweise wird extern verwendet, um die Linkage von Objekten im
Static Storage festzulegen.
In C werden Objekte per Identifier referenziert, und der
Gültigkeitsbereich eines Identifiers ist maximal eine Compilation
Unit. Um ein Objekt von einer anderen Compilation Unit aus zu
referenzieren als von der, die es definiert, gibt es in C External
Linkage. Diese dient quasi dazu, Identifier über Compilation Units
hinweg so zu "verbinden", dass sie sich auf das gleiche Objekt (immer
Speicherklasse Static Storage) beziehen.
Um das ganze verwirrender zu machen, kann man External Linkage auch ohne
"extern" erreichen, und "extern" kann sich auf eine tentative
Declaration beziehen:
1
staticintx;// Tentative Decl (Internal Linkage)
2
externintx;// Bezieht sich auf "static int x".
3
staticintx=2;// Definition mit Internal Linkage.
aber:
1
externintx;// Decl x (External Linkage).
2
staticintx;// Error.
oder
1
staticintx;// Tentative Decl
2
externintx;// Bezieht sich auf "static int x".
3
// End of File => Tentative Decl wird zue Definition "static int x = 0".
oder
1
intx;// Tentative Decl, External Linkage
2
// End of File => Tentative Decl wird zur Definition "int x = 0".
Um die Sache weiter zu verkomplizieren, kann man (Legacy) extern
tentative Decls in mehreren Modulen haben, die dann in einer Common
Section (.comm) "überlagert" werden — zumindest wenn der Compiler das
implementiert, was zum Beispiel bei GCC der Fall ist (-fcommon ist immer
noch Default). Wenn man das nicht möchte, compiliert man mit
-fno-common, und erst dann meckert der Linker "multiple definition of"
an. Definitionen (nicht tentative) kommen in eine andere Section (bei
elf-Targets üblicherweise .bss, .data oder .rodata), die dieses common
Feature nicht haben, und wenn mehrfach versucht wird, das gleiche Symbol
in so eine Section zu lokatieren, meckert der Linker (auch mit
-fcommon).
Ok, das waren die Stolperfallen und Seltsamkeiten die man üblicherweise
nicht braucht. Stattdessen hat man i.d.R folgende 3 Anwendungsfälle:
1) External Linkage: Verwendung in mehreren Compilation Units:
1
externintx;// Decl im Header
2
intx=2;// Definition in *einem* Modul
2) Internal Linkage: Verwendung in einer Compilation Unit:
1
staticintx=2;// Decl im Modul
3) No Linkage (und Static Storage) Verwendung in einer Funktion:
Daniel S. schrieb:> Ich glaube ich verstehe nicht ganz was die Zeile mit dem "extern" in der> Header-Datei darstellen soll.> Auf alle drei Dateien bezogen:> Wie viele Definitionen / Deklarationen gibt es?> -> gibt es drei verteilt auf alle drei Dateien> -> oder gibt es nur zwei, verteilt auf die Dateien mit der Endung .c
main.c:6
1
intersteVariable=13;/*<- 1*/
Das ist eine Definition mit Initialisierung (offiziell "external
definition" genannt). Sie sorgt u.a. dafür, dass für die Variable
Speicher reserviert wird. So eine "external definition" darf für einen
bestimmten Variablennamen im gesamten Programm einschließlich der
hinzugelinkten Module und Bibliotheken nur ein einziges Mal auftauchen,
sonst wird eine Fehlermeldung ausgegeben. Da Header-Files i.Allg. in
mehreren Übersetzungseinheiten genutzt werden, findet man dort i.Allg.
keine Definitionen dieser Art.
erste_Datei.h:6
1
externintersteVariable;/*funktioniert auch prima ohne "extern"*/
Das ist eine reine Deklaration (ich schreibe hier "reine", weil auch
Definitionen Deklarationen sind). Sie teilt dem Compiler lediglich mit,
dass irgendwo eine Variable dieses Namens und dieses Typs existiert,
veranlasst aber keine Speicherreservierung. Sie darf im gesamten
Programm beliebig oft auftauchen, weswegen man sie oft in Header-Files
findet.
erste_Datei.c:6
1
intersteVariable;/*Fehler bei Initialisierung an dieser Stelle*//*<- 2*/
Das ist eine Definition ohne Initialisierung (offiziell "tentative
definition" genannt). Sie darf innerhalb einer Übersetzungseinheit
beliebig oft auftauchen, verweist dabei aber immer auf dasselbe Objekt
und damit denselben Speicherort. Enthalten andere Übersetzungseinheiten
ebenfalls Definitionen für denselben Namen, hängt ihre Behandlung von
den Compiler-Optionen ab:
Default bzw. mit -fcommon: sämtliche im gesamten Programm auftretenden
"tentative definitions" und ggf. die (höchstens einmal vorhandene)
"external definition" werden zu einem einzigen Objekt zusammengefasst.
Existiert keine "external definition", wird dennoch Speicher reserviert
und dieser mit 0 initialisiert.
Mit -fno-common: Das im vorigen Abschnitt (Default) beschriebene
Vorgehen wird nicht auf das gesamte Programm, sondern auf jede einzelne
Übersetzungseinheit angewandt. Stellt sich am Ende heraus, dass die
Variable in mehreren Übersetzungseinheiten definiert wurde, wird eine
entsprechende Fehlermeldung ausgegeben.
Daniel S. schrieb:> Ich frage mich also nach welchen Kriterien der Compiler nun entscheidet,> welche Definition (1 oder 2) er nun auch wirklich als Definition zulässt
Beide :-) #1 Führt zur Definition eines globalen Symbols in .data (siehe
oben was ich zu tentative Decl schrieb), und #2 ergibt ein globales
Symbol in .comm (mit -fcommon, GCC-Default). Der Linker legt dies
jedoch nicht nach COMMON, da er zusätzlich eine Definition in .data
vorfindet. Die genauen Regeln und historischen Absonderlichkeiten kenn
ich auch nicht, aber in 99.99% der Fälle fährt man besser mit
-fno-common: Wie oben erklärt landet dann #2 in .bss und der Linker
meckert wie erhofft "multiple definition of 'x'".
> und welche er in eine Deklaration umwandelt.
Keine. Aber #2 wird am Ende der Compilation Unit von einer tentative
Decl zu einer Definition (mit external Linkage).
Vielleicht noch ein Paar Worte zur Historie des "extern"-Schlüsselworts,
die dieses Durcheinander mit und ohne "extern" etwas nachvollziehbarer
macht:
Der erste C-Compiler von Ritchie lief auf einer PDP-11 unter Unix,
dessen Linker in der Lage war, mehrere Definitionen einer globalen
Variable zusammenzufassen, so wie dies auch heute unter Unix noch der
Fall ist. Das "extern" diente lediglich dazu, innerhalb einer Funktion
eine Variable als global zu deklarieren. Mit "extern" war also nicht
"außerhalb der Übersetzungseinheit", sondern "außerhalb der Funktion"
gemeint. In Deklarationen außerhalb von Funktionen (ISO: "external
declarations") brauchte man "extern" nicht, und es wurde einfach
ignoriert, wenn man es trotzdem hinschrieb. Da auch "extern" innerhalb
von Funktionen nur sehr selten benötigt werden, wurde es deswegen so gut
wie überhaupt nicht verwendet. Auch heute noch wird es zumindest bei der
Deklaration von Funktionen immer noch weggelassen. So war das eigentlich
eine runde und in sich konsistente Sache.
C wurde aber auch auf andere Plattformen portiert, wo der Linker dieses
Zusammenfassen gleichnamiger Variablen nicht beherrschte. Hier musste
eine globale Variable explizit einer einzigen Übersetzungseinheit
zugeordnet werden, in allen anderen durfte keine Variable desselben
Namens angelegt werden. Trotzdem musste natürlich auch in diesen die
Variable irgenwie deklariert werden. Also entschied Ritchie, diese
Deklaration mit dem an dieser Stelle sonst redundanten "extern" zu
kennzeichnen.
Zitat aus dem "C Reference Manual" von Dennis M. Ritchie:
1
In other operating systems, however, the compiler must know in just
2
which file the storage for the identifier is allocated, and in which
3
file the identifier is merely being referred to. In the implementations
4
of C for such systems, the appearance of the extern keyword before an
5
external definition indicates that storage for the identifiers being
6
declared will be allocated in another file. Thus in a multi-file
7
program, an external data definition without the extern specifier must
8
appear in exactly one of the files. Any other files which wish to give
9
an external definition for the identifier must include the extern in the
10
definition. The identifier can be initialized only in the file where
11
storage is allocated.
12
13
In PDP-11 C none of this nonsense is necessary and the extern specifier
14
is ignored in external definitions.
Die heutigen unixoiden Linker sind natürlich mindestens genauso schlau
wie der Linker auf der PDP-11, so dass es eigentlich keinen Grund gibt,
das "extern" in globalen Deklarationen zu erzwingen. Aus Rücksicht auf
zurückgebliebene Linker unter anderen Betriebssystemen (ich glaube, der
Microsoft-Linker gehört(e) auch dazu), hat es sich aber eingebürgert,
das "extern" genauso zu verwenden, wie in obigem Zitat beschrieben, auch
wenn das "Nonsense" ist :)
Johann L. schrieb:> Um das ganze verwirrender zu machen, kann man External Linkage auch ohne> "extern" erreichen, und "extern" kann sich auf eine tentative> Declaration beziehen:static int x; // Tentative Decl (Internal Linkage)> extern int x; // Bezieht sich auf "static int x".> static int x = 2; // Definition mit Internal Linkage.
Das Symbol x ist aber außerhalb der Übersetzungseinheit nicht
erreichbar, denn es ist ja static:
1
haku:~/temp/ld $ nm main.o
2
0000000000000000 D ersteVariable
3
U initialisiereVariable
4
0000000000000000 T main
5
U printf
6
0000000000000004 d x
7
0000000000000004 C zweiteVariable
W.S. schrieb:> Bei externen Variablen muß man das extern in der .h davorschreiben, da> diese ja auch in andere Quellen inkludiert wird.
Muss man ja gerade nicht, wegen der tentativen Deklarationen. Genau
darum gehts ja gerade.
Sven P. schrieb:> Johann L. schrieb:>> static int x; // Tentative Decl (Internal Linkage)>> extern int x; // Bezieht sich auf "static int x".>> static int x = 2; // Definition mit Internal Linkage.> Das Symbol x ist aber außerhalb der Übersetzungseinheit nicht> erreichbar,
Das braucht es auch nicht, mein Code ist in einer Compilation Unit
(ansonsteen hätte ich das natürlich dazu geschrieben). Frag mich aber
nicht, wozu das gut ist (wenn man wirklich sich nochmal auf x beziehen
will — warum auch immer — dann ginge das ja auch mit "static int x;"
statt dem "extern int x;".
Johann L. schrieb:> Das braucht es auch nicht, mein Code ist in einer Compilation Unit> (ansonsteen hätte ich das natürlich dazu geschrieben).
Achso, von der Seite habe ich das noch garnicht gesehen.
Man bezieht sich quasi mit der extern-Deklaration auf ein globales
Symbol, welches man gleich danach auch definiert, aber nicht
exportiert...
Naja. Obwohl das nicht ganz korrekt ist, habe ich mir in meinem Weltbild
extern und static in etwa symmetrisch hingelegt. Entweder ist etwas eine
extern-Deklaration, dann kommt es (z.B. per Linker) von irgendwoher.
Oder es ist eine static-Definition, dann soll es ausdrücklich
nirgendwoher kommen und nur in der Übersetzungseinheit bleiben.
Das erscheint mir auch insofern sinnvoll, als dass die
static-Speicherklasse quasi die Lebensversicherung dafür ist, nicht
versehentlich mit fremden Symbolen zu kollidieren.
Sven P. schrieb:> Johann L. schrieb:>> Das braucht es auch nicht, mein Code ist in einer Compilation Unit>> (ansonsten hätte ich das natürlich dazu geschrieben).> Achso, von der Seite habe ich das noch garnicht gesehen.>> Man bezieht sich quasi mit der extern-Deklaration auf ein globales> Symbol, [...]
Nein, in dem speziellen Beispiel ist das Symbol nicht global (internal
Linkage, keine external Linkage) und dennoch kann man sich mit
extern darauf beziehen.
Hallo,
da bin ich wieder.
Entschuldigt das ich mich so lange nicht gemeldet habe.
Ich war die letzten Tage leider ziemlich verplant, daher...
Es hat sich ja eine rege Unterhaltung entwickelt.
Die habe ich natürlich auch mitgelesen.
Dabei ist mir allerdings von Beitrag zu Beitrag mehr klar geworden, dass
ich offensichtlich ein viel grundlegenderes Problem habe.
Mir fehlt Wissen über den Compiler und den Kompiliervorgang an sich.
Das verwundert natürlich nicht, da ich mich gerade erst am Anfang
befinde.
Trotzdem drängen sich mir einige Fragen auf, welche ich zur
unkoplitzierten Klärung einfach mal hier in den Raum stellen möchte.
Sollten das bereits Themen für neue Threads sein, bitte ich einfach um
eine kurze Rückmeldung diesbezüglich.
Vor allem habe ich aber ein Quellenproblem.
Es wäre nicht so, dass ich keine Bücher zur Thematik hier liegen hätte.
Leider beziehen sie sich auf die reine Programmierung. Da mich aber auch
interessiert was im Hintergrund passiert, scheinen solche Quellen für
mich einfach nicht ausreichend zu sein.
Internetquellen sind auch so eine Sache. Manche stimmen, manche nicht.
Sobald sie sich wiedersprechen gibt es ein Entscheidungsproblem, was in
der Regel beide Quellen nutzlos macht.
Deswegen gleich meine erste Frage:
-> Kann mir jemand ein Buch zur Thematik empfehlen?
Meine weiteren Fragen sind folgende:
-> Weswegen brauche ich überhaupt einen Header? Könnte ich nicht einfach
direkt die .c-Datei einbinden (in diese wären die Funktionsdeklarationen
dann direkt eingebunden.
-> Wann im Kompiliervorgang werden die Funktionsdeklarationen im Header
gegen die echten Implementierungen getauscht? Beim Linken?
-> Wieso muss ich die Variable ersteVariable im Header deklarieren? Kann
ich sie nicht einfach direkt in der passenden .c-Datei definieren? Was
wären die Folgen?
Johann L. schrieb:> Sven P. schrieb:>> Johann L. schrieb:>>> Das braucht es auch nicht, mein Code ist in einer Compilation Unit>>> (ansonsten hätte ich das natürlich dazu geschrieben).>> Achso, von der Seite habe ich das noch garnicht gesehen.>>>> Man bezieht sich quasi mit der extern-Deklaration auf ein globales>> Symbol, [...]>> Nein, in dem speziellen Beispiel ist das Symbol nicht global (internal> Linkage, keine external Linkage) und dennoch kann man sich mit> extern darauf beziehen.
Tippfehler, lokales Symbol natürlich. Es wird ja grad nicht
exportiert...
Daniel S. schrieb:> Quellen
Dein Quellenproblem ist nachvollziehbar. Die Erklärungen von mir und
Johann kann man leider noch am besten bei uralten Compilern bzw. in
alter Literatur nachvollziehen. Mit aktuellen Toolchains ist der
Compiler mit dem Linker heute ziemlich verzahnt.
> -> Weswegen brauche ich überhaupt einen Header? Könnte ich nicht einfach> direkt die .c-Datei einbinden (in diese wären die Funktionsdeklarationen> dann direkt eingebunden.
Im Prinzip schon, den Compiler juckts nicht. Allerdings würdest du ja
dann auch die Definitionen immer wieder mit einbinden und dadurch
wiederholen. Das willst du im Falle globaler Variablen ja gerade nicht
(wobei die tentativen Definitionen ja ein bisschen Ausnahme sind).
Zum Beispiel würden static-Variablen dann mehrfach im Speicher erzeugt.
Eventuell ist dein Linker so klug und sammelt die Funktionsdefinitionen
(also die Rümpfe) nachher wieder zusammen, ansonsten hast du auch die
nachher mehrfach im Speicher.
> -> Wann im Kompiliervorgang werden die Funktionsdeklarationen im Header> gegen die echten Implementierungen getauscht? Beim Linken?
Ja. Aber nicht nur die Funktionsdeklarationen, auch die globalen
Variablen. Tauschen ist auch Ansichtssache. Der Compiler setzt einfach
nur Platzhalter-Adressen ein, wann immer eine Variable oder Funktion
braucht, von der er keine Definition (sondern nur eine Deklaration) hat.
Der Linker ersetzt diese Platzhalter anschließend durch echte Adressen.
Der Linker ist nämlich auch derjenige, der den ganzen Kram im Speicher
anordnet und Platz für Variablen verteilt, also kennt er die Adressen.
> -> Wieso muss ich die Variable ersteVariable im Header deklarieren? Kann> ich sie nicht einfach direkt in der passenden .c-Datei definieren? Was> wären die Folgen?
Definieren oder Deklarieren?
Normalerweise will man sie im Header deklarieren (mit extern), damit
andere Leute, die den Header einbinden, darauf zugreifen können. Im
Prinzip könntest du sie auch kurz vor Verwendung händisch deklarieren
(mit extern). Im Header ist es aber praktischer, weil der eine schöne
Schnittstelle bietet. Normalerweise betreust du als Entwickler ja eine
Source mitsamt Header und kannst beide zusammen pflegen.
Das heißt, in deiner Source hast du die Definition und in deinem Header
machst du sie per Deklaration bekannt, damit andere Leute deinen
Programmteil nutzen können.
In den Header kommen die Deklarationen rein, die eine andere .c Datei
braucht, um deine .c benutzen zu können.
Nicht mehr.
Es sind die Schnittstelleninformationen.
Natürlich nurnötig, wenn du auch mehrere .c benutzt.
Sven P. schrieb:>> -> Wieso muss ich die Variable ersteVariable im Header deklarieren? Kann>> ich sie nicht einfach direkt in der passenden .c-Datei definieren? Was>> wären die Folgen?> Definieren oder Deklarieren?> Normalerweise will man sie im Header deklarieren (mit extern), damit> andere Leute, die den Header einbinden, darauf zugreifen können. Im> Prinzip könntest du sie auch kurz vor Verwendung händisch deklarieren> (mit extern). Im Header ist es aber praktischer, weil der eine schöne> Schnittstelle bietet. Normalerweise betreust du als Entwickler ja eine> Source mitsamt Header und kannst beide zusammen pflegen.> Das heißt, in deiner Source hast du die Definition und in deinem Header> machst du sie per Deklaration bekannt, damit andere Leute deinen> Programmteil nutzen können.
Ich meine wirklich definieren.
Ich kann mir aber fast schon die Antwort darauf geben.
Steht die Deklaration nicht im Header, kann im Falle das die Variable
nicht schon bereits in der einbindenden Datei existiert auch nicht auf
die Variable zugegriffen werden.
Sie ist schlicht nicht bekannt.
So wird der Header eingebunden, die Variable "vorgestellt" und in der
zugehörigen .c-Datei definiert.
Wird sie nur dort definiert, ist sie im einfachsten Fall eben auch nur
dort verfügbar.
Das ist offensichtlich auch der Grund warum es Header-Files gibt.
Mir ist einfach noch nicht ganz klar, warum ich nicht ANSTELLE der
Header-Datei nicht einfach die ganze .c-Datei einbinde.
Sven P. schrieb:> Das erscheint mir auch insofern sinnvoll, als dass die> static-Speicherklasse quasi die Lebensversicherung dafür ist, nicht> versehentlich mit fremden Symbolen zu kollidieren.
Sollte kein Problem sein, denn heutzutage gibt es beim Linker anstelle
eines gelinkten Files eine geharnischte Fehlermeldung, wenn es mehrere
gleichnamige Variablen oder Funktionen in verschiedenen Modulen gibt.
Das static kann man deshalb getrost weglassen - sofern man nicht
dediziert exakt gleichnamige globale Variablen in jeweils mehreren
Modulen habe will.
Yalu X. schrieb:> Die heutigen unixoiden Linker sind natürlich mindestens genauso schlau> wie der Linker auf der PDP-11, so dass es eigentlich keinen Grund gibt,> das "extern" in globalen Deklarationen zu erzwingen. Aus Rücksicht auf> zurückgebliebene Linker unter anderen Betriebssystemen
Das ist mal wieder eine recht seltsame Weltsicht, die hier zutage kommt.
Das Normale bei nicht zurückgebliebenen Programmiersprachen ist, daß man
vor externen Entitäten eben das "extern" davorschreiben sollte oder
einen anderen Weg der Kennzeichnung wählen sollte, um die Tatsache der
Externität zu dokumentieren und den Compiler nicht vergeblich nach der
eigentlichen Deklaration dieser Entität innerhalb der vorhandenen Quelle
suchen zu lassen.
Aber C kennt ja von alldem nichts und hat auch keinerlei Modulsystem -
und die Sichtweise von K&R ist einfach nur krank. Das ist noch viel
schlimmer als die vorgefaßten Typzuweisungen in Fortran bei i,j,k,l...
Also: Zurückgeblieben ist lediglich die Sichtweise von Ritchie und der
Compiler der PDP-11. Das ist alles.
W.S.
Daniel S. schrieb:> Das ist offensichtlich auch der Grund warum es Header-Files gibt.> Mir ist einfach noch nicht ganz klar, warum ich nicht ANSTELLE der> Header-Datei nicht einfach die ganze .c-Datei einbinde.
Kannst du machen, aber in diesem Falle darfst und brauchst du diese
Datei auch garnicht separat zu übersetzen und erst recht nicht ins
Projekt mit zu linken, denn sie iest damit ja bereits enthalten.
Das ist alles ein altertümliches Kuddelmuddel.
Also merke dir - im direkten Gegensatz zu den Auffassungen von Yalu -
dieses:
In .h Dateien kommt - so wie es ist - nur so etwas hinein, was selbst
keinen Speicherplatz kostet. Also Typdeklarationen und Konstanten per
#define.
Alles, was hingegen Speicherplatz kosten würde, kommt nur mit
vorgestelltem extern hinein - einfach um damit anzuzeigen, daß das in
der Headerdatei Befindliche nur ein Verweis ist auf etwas, das woanders
tatsächlich steht.
Und Ritchie kann im Jahre 2019 seinen Drang zur unsauberen
Programmierung einfach mal steckenlassen. Gilt auch für seine
Apologeten.
W.S.
W.S. schrieb:> Sollte kein Problem sein, denn heutzutage gibt es beim Linker anstelle> eines gelinkten Files eine geharnischte Fehlermeldung, wenn es mehrere> gleichnamige Variablen oder Funktionen in verschiedenen Modulen gibt.> Das static kann man deshalb getrost weglassen - sofern man nicht> dediziert exakt gleichnamige globale Variablen in jeweils mehreren> Modulen habe will.
Nein, gibt es eben nicht. Gleichnamige Variablen sammelt der Linker in
der .comm-Sektion auf und macht globale draus. Das hat Johann aber oben
auch länglich erklärt.
Das static ist unbedingt notwendig, weil man ja eben nicht weiß, was in
anderen Modulen vereinbart wird.
Daniel S. schrieb:> Mir ist einfach noch nicht ganz klar, warum ich nicht ANSTELLE der> Header-Datei nicht einfach die ganze .c-Datei einbinde.
Naja ganz einfach, weil du den Inhalt des Headers (also die
Variablen-Vorstellungen, wie du ziemlich treffend geschrieben hast)
möglicherweise auch in mehreren anderen Sourcen benötigst. Und wenn du
in jeder dieser Sourcen gleich die ganze .c-Datei einbindest, dann hast
du es doppelt.
Abgesehen davon wird die Variable ja durch eine Deklaration schon
bekannt, bevor sie definiert wird (Vorwärts-Deklaration). Das ist
manchmal notwendig, weil es manchmal schlicht keine mögliche Reihenfolge
gibt, in der man alle .c-Dateien in zusammenkopieren könnte. Das
passiert zum Beispiel, wenn sich zwei Funktionen gegenseitig aufrufen.
Ohne eine Deklaration von einer der beiden würde das nicht gehen, denn
egal wie herum die die Funktionen in die .c-Datei schreibst: die erste
kennt die zweite nicht. Außer du spendierst eine Deklaration vorab.
Abgesehen von dem K&R-Gesabbel ist folgende aber eine ganz passable
Merkregel von W.S.:
W.S. schrieb:> In .h Dateien kommt - so wie es ist - nur so etwas hinein, was selbst> keinen Speicherplatz kostet.
Okay, danke für eure vielen Antworten erst einmal.
Ich denke das sollte bei der tollen Resonanz hier nicht zu kurz kommen.
Ich suche eigentlich eine sehr hardwarenahe Programmiersprache mit der
ich für ein Linux-Betriebssystem programmieren kann.
Da Linux größtenteils in C geschrieben ist dachte ich, das dies auch die
geeignete Sprache der Wahl ist.
Inzwischen bin ich mir aber nicht mehr sicher ob ich C überhaupt jemals
richtig lernen kann.
Es scheint unglaublich viele Varaiblen zu geben, die für einen Anfänger
nur wirklich schwer einzusehen sind.
Selbst eingearbeitete Profis sind sich bei einigen Dingen - zumindest
uneins.
Nicht zuletzt hatte ich C ausgewählt, weil es auch recht populär ist.
Bitte versteht mich nicht falsch. Ich möchte hier nichts infrage stellen
und schon gar nicht will ich jemandem auf die Füße treten.
Ich frage mich lediglich, ob es für mich als Einsteiger nicht eine
bessere Wahl gibt.
Die Sprache die ich suche sollte:
-> statisch typisiert sein
-> möglichst hardwarenah sein
-> geschriebene Programme sollten maximal portabel sein
-> ich möchte Systemaufrufe in Linux umsetzen können und auch ganz
allgemein höchstmögliche Linuxkompatibilität haben
-> die Sprache sollte gut dokumentiert sein und deutsche Literatur
sollte verfügbar sein
-> die Sprache sollte in sich konsistent sein
-> die Sprache sollte zukunftssicher sein
Sicher habe ich mich noch nicht gegen C entschieden.
Ich will einfach nur sicherstellen, dass ich mich hier nicht in
Unmöglichkeiten vergallopiere.
Wenn ich mehr Zeit mit Raten verbringen würde statt mit verstehen, wäre
C ganz eindeutig falsch für mich.
Was meint Ihr denn dazu?
Daniel S. schrieb:> Was meint Ihr denn dazu?
Fang einfach mit C an.
Bis jetzt scheinst du doch alles verstanden zu haben?
Linkage ist nunmal da, auch bei anderen Sprachen. Häng das nicht so hoch
auf und mach dir nicht so einen Kopf darum bei akademischen Beispielen.
Wenn es in deinem Programm so weit ist, dann wird sich das ganz
geschmeidig einfügen.
Und richtig lernen. Ich denke, 50% der "Programmierer" in der Industrie
können nicht richtig mit ihren Werkzeugen umgehen. Das sehe ich jedes
Mal wieder, wenn ich fremden Quelltext angucke, der aus der "Industrie"
kommt.
Allein damit, dass du die ganze Geschichte mit der Linkage und der
Speicherklasse überhaupt mal durchdacht hast, hast du davon vermutlich
schon mehr kapiert, als deine Kollegen... Und da du ja ohnehin systemnah
arbeiten willst, ist das doch ein optimaler Weg.
Okay, ich denke ich werde mich einfach da durchbeißen, möglichst ohne
mich zu verbissen an etwas aufzuhängen.
Wahrscheinlich versteht man viele Zusammenhänge auch erst wenn man
unabhängig an verschiedenen Stellen tiefer in die Materie eingedrungen
ist.
Ich denke ich habe hier auch eine sehr gute Anlaufstelle um Fragen zu
Stellen.
Von daher rechne ich mir meine Chancen schon mal nicht so schlecht aus.
Ich hatte eben deswegen gefragt, weil ich einfach auch mal die Meinung
von jemandem hören wollte, der sich besser mit C auskennt und daher auch
das Naturell der Sprache an sich einschätzen kann.
Aber wenn man mir das an der Stelle zutraut bin ich beruhigt.
:-)
Daniel S. schrieb:> Inzwischen bin ich mir aber nicht mehr sicher> ob ich C überhaupt jemals richtig lernen kann.
C ist eine relativ kleine Sprache, mit einer relativ kleinen
Standardbibliothek. Das macht es grundsätzlich zu einer einfach zu
lernenden Sprache.
Allerdings hat C einige Eigenheiten, die historisch gewachsen sind und
einstmals sinnvoll waren, es heute aber nicht mehr unbedingt sind. Von
deren Existenz muss man zumindest wissen, um Fehler vermeiden zu können.
Und dann wäre da noch der Fakt, dass C mit sich alles machen lässt, was
irgendwie einigermaßen möglicherweise sinnvoll sein könnte. Das macht C
zu einer komplexen und äußerst mächtigen Sprache.
Die restliche Infrastruktur um C herum (Compiler, Linker, ...) ist
ähnlich primitiv wie der eigentliche Sprachkern und erfordert, dass man
die Grundlagen verstanden hat.
Moderne Sprachen verheimlichen die interne Funktionsweise, teilweise bis
zum Punkt "hier klick für START und dort klicken für EXE", was prima
funktioniert, bis es mal nicht funktioniert. Dann muss man das alles
trotzdem lernen, es wird einem aber besonders schwer gemacht.
Daniel S. schrieb:> Selbst eingearbeitete Profis sind sich bei> einigen Dingen - zumindest uneins.
Programmieren (Programmentwicklung) ist an sich eine kreative Tätigkeit,
im Gegensatz zum recht mechanischem Code-Tippen. Da gibt es oft kein
"richtig" oder "falsch", sondern unterschiedliche Meinungen mit jeweils
eigenem Für und Wider, die man zudem ständig neu bewerten muss. Die
Realität erfordert ständig Kompromisslösungen.
Zum Abschluss wäre zu sagen: Deine Probleme scheinen nicht "C lernen" zu
sein, sondern "Programmieren lernen". Wenn du das einmal gründlich durch
hast, dann ist die Sprache eher egal.
Der Compiler muss nur die Eigenschaften aber nicht die Lage von
Variablen oder Funktionen kennen.
Eigenschaften der Variablen sind z.B. die Größe oder die
Veränderbarkeit.
Warum? Weil der Compiler je nach der Größe (char, int, long, float . ..)
der Variablen einen unterschiedlichen Maschinencode einsetzen muss.
Beispiele: char => mov.b int => mov.w bei float reicht
meist ein einzelner Maschinencode nicht aus. Es wird ein komplettes
Unterprogramm aus einer Bibliothek eingefügt.
Auch ob die Variable vorzeichenbehaftet oder vorzeichenlos (unsigned)
ist, hat Einfluss auf den vom Compiler eingesetzten Maschinencode.
Wird eine Variable mit der Eigenschaft const versehen, dann verbietet
der Compiler jeglichen Maschinencode, der eine Veränderung bewirken
würde. Die Eigenschaft volatil untersagt dem Compiler Optimierungen mit
der Variablen durchzuführen. Einzig, die AdressInformation, wo die
Variable liegt, bleiben bei der Erzeugung des Maschinencodes offen, es
wird nur der Platz in der Objektdatei reserviert, der dann vom Linker
ausgefüllt wird.
Für Funktionen gelten ähnliche Gründe, z.B bei der Parameterübergabe.