www.mikrocontroller.net

Forum: Compiler & IDEs Frage GCC-Tutorial: EEPROM-Variable auf feste Adressen legen


Autor: Tobias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich arbeite gerade (theoretisch) das 
http://www.mikrocontroller.net/articles/AVR-GCC-Tu... 
durch. Dazu habe ich folgende Frage:
#include "eeprom.h"          // Eigene EEPROM-Headerdatei einbinden
 
uint8_t ee_mem[EESIZE] EEMEM =
{
   [EE_DUMMY]   = 0x00,
   [EE_VALUE1]  = 0x05,
   [EE_WORD1L]  = 0x01,   
   [EE_WORD1H]  = 0x00,
   [EE_VALUE2]  = 0xFF
};

Warum steht z.B. EE_DUMMY in eckigen Klammern bzw. welche Bedeutung hat 
das? Ich als Anfänger kenne die eckigen Klammern in C bisher nur als 
Index bei Arrays, was hier aber wohl nicht der Fall ist.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tobias schrieb:
> Hallo,
>
> ich arbeite gerade (theoretisch) das
> 
http://www.mikrocontroller.net/articles/AVR-GCC-Tu...
> durch. Dazu habe ich folgende Frage:
>
>
> #include "eeprom.h"          // Eigene EEPROM-Headerdatei einbinden
> 
> uint8_t ee_mem[EESIZE] EEMEM =
> {
>    [EE_DUMMY]   = 0x00,
>    [EE_VALUE1]  = 0x05,
>    [EE_WORD1L]  = 0x01,
>    [EE_WORD1H]  = 0x00,
>    [EE_VALUE2]  = 0xFF
> };
> 
>
> Warum steht z.B. EE_DUMMY in eckigen Klammern bzw. welche Bedeutung hat
> das?

Scroll ein wenig nach oben im Tutorial. Da gibt es ein paar #define 
dafür.
#define   EE_DUMMY   0x000  // Dummyelement (Adresse 0 sollte nicht genutzt werden)
#define   EE_VALUE1  0x001  // Eine Bytevariable  
#define   EE_WORD1L  0x002  // Eine Wordvariable (Lowbyte)
#define   EE_WORD1H  0x003  // Eine Wordvariable (Highbyte)
#define   EE_VALUE2  0x004  // Eine weitere Bytevariable 

Letzendlich steht da
uint8_t ee_mem[EESIZE] EEMEM =
{
   [0x000]  = 0x00,
   [0x001]  = 0x05,
   [0x002]  = 0x01,
   [0x003]  = 0x00,
   [0x004]  = 0xFF
};

Das ganze ist die Initialisierung eines Arrays und jetzt sollte es nicht 
mehr schwer zu erraten sein, was die Bedeutung ist.

Autor: Tobias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank, wieder was dazugelernt. Diese Art von Arrayinitialisierung 
hatte ich vorher noch nirgends gesehen. Selbst im berühmt/berüchtigten 
K&R kann ich mich nicht dran entsinnen. Ich kannte bisher nur die 
"normale" Initialisierung in geschweiften Klammern, wo sich die 
Reihenfolge der Elemente automatisch ergibt.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tobias schrieb:
> Vielen Dank, wieder was dazugelernt. Diese Art von Arrayinitialisierung
> hatte ich vorher noch nirgends gesehen. Selbst im berühmt/berüchtigten
> K&R kann ich mich nicht dran entsinnen. Ich kannte bisher nur die
> "normale" Initialisierung in geschweiften Klammern, wo sich die
> Reihenfolge der Elemente automatisch ergibt.

Ich bin mir nicht mal sicher, ob diese Initialisierung standardkonform 
ist, oder ob das wieder mal eine Spezialität des gcc darstellt.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es ist GNU-C.

Die Definition von EESIZE ist übrigens überflüssig bzw. wird besser 
gemacht in der Form
#include <avr/io.h>

#define EESIZE (1+E2END)

denn in den I/O-Headern wird die letzte EEPROM-Adresse als E2END 
definiert.

Die vorgeschlagene Lösung feste Adressen durch Anlegen eines Arrays 
maximaler Größe zu definieren, gefällt mir übrigens überhaupt nicht.
Das Layout muss komplett handgefrickelt werden. Ausserdem müssen alle 
Elemente vom gleichen Typ sein.

Der einzige Grund, feste Adressen zu vergeben, sind Sachen wie 
Seriennummern oder Checksummen. Für alles andere ist die absolute 
Adresse wurscht.

Daher wäre es besser, die üblichen Nutzdaten in einer Struktur zu 
organisieren. Dies hat den weiteren Vorteil, daß man ein Abbild der 
Struktur im RAM anlegen und problemlos darauf zugreifen kann. Die 
Struktur wird im Startup einmal geladen und nur rückgeschrieben, wenn 
sich was geändert hat (und Änderungen an der Konfiguration nicht 
verworfen werden durch ein "Cancel", sondern wirklich dauerhaft ins 
EPROM sollen).
#include <avr/io.h>
#include <avr/eeprom.h>

typedef struct
{
    uint8_t  wert;
    uint16_t anzahl;
    char     name[10];
    uint8_t  wertigkeit;
} eeprom_t;

// GNU-C, ist lesbarer
eeprom_t eeprom EEMEM = 
{
    .wert = 1,
    .anzahl = 0x1234,
    .name = "Hallo",
    .wertigkeit = 8
};

// Standard-C: Reihenfolge einhalten!
eeprom_t eeprom EEMEM = 
{
    1, 0x1234, "Hallo", 8
};

eeprom_t eeprom_ram;

void init (void)
{
    eeprom _read_block (& eeprom_ram, & eeprom, sizeof (eeprom_t));
}

Die .eeprom-Section wird dann an die gewünschte Adresse gelegt, 
üblicherweise ist das 1 weil man 0 aussparen sollte. Als 
Makefile-Schnipsel:
%_eeprom.hex: %.elf
  $(OBJCOPY) -j .eeprom --change-section-lma .eeprom=1 -O ihex $< $@

bzw. ausgetextet:
%_eeprom.hex: %.elf
  avr-objcopy -j .eeprom --change-section-lma .eeprom=1 -O ihex foo.elf foo_eeprom.hex

Falls der seltene Fall vorliegt, daß man wirklich explizite Adressen 
braucht, dann lässt man .eeprom zB an Adresse 3 beginnen und behandelt 
die eine 16-Bit-Variable von Hand:
#define SERNUM_ADDR ((uint16_t*) 1)
...
{
    uint16_t sernum = eeprom_read_word (SERNUM_ADDR);
}
...

Johann

Autor: Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Kann man nicht einfach in einer .c-Datei des Projektes sowas wie
...
#define EEPROM_ADDRESS_1 33
#define EEPROM_ADDRESS_2 34
...
eeprom_write_byte ((uint8_t *) EEPROM_ADDRESS_1, 'T') 
eeprom_write_byte ((uint8_t *) EEPROM_ADDRESS_2, 6)
...
schreiben? Es sollte dann doch eine .eep-Datei erstellt werden, die so 
mit exakt dem Inhalt im EEPROM landet, ohne das eigentliche Programm im 
Flash auch nur irgendwie zu belasten?

Autor: Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nachtrag: Es müßte natürlich gar keine Datei im Projekt sein. Es kann ja 
irgendein Programm sein, was einem praktisch diesen .eep-Teil erzeugt, 
den man seinem Projekt hinzufügen kann (ruder zurück ...)

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lutz schrieb:
> Kann man nicht einfach in einer .c-Datei des Projektes sowas wie
>
>
> ...
> #define EEPROM_ADDRESS_1 33
> #define EEPROM_ADDRESS_2 34
> ...
> eeprom_write_byte ((uint8_t *) EEPROM_ADDRESS_1, 'T')
> eeprom_write_byte ((uint8_t *) EEPROM_ADDRESS_2, 6)
> ...
> 
> schreiben? Es sollte dann doch eine .eep-Datei erstellt werden, die so
> mit exakt dem Inhalt im EEPROM landet, ohne das eigentliche Programm im
> Flash auch nur irgendwie zu belasten?

Das Programm wird durch eine handhabbare Datenorganisation dirch 
Strukturen icht belastet. Das ist Fingerübung für'n Compiler.

Adressen hart zu vergeben mag verlockend aussehen und schnell zu 
Ergebnissen führen, aber es ist fehlerträchtig und nervig das händisch 
zu verwalten.

Johann

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johann L. schrieb:

> Die Definition von EESIZE ist übrigens überflüssig bzw. wird besser
> gemacht in der Form
>
> #include <avr/io.h>
> 
> #define EESIZE (1+E2END)
> 
>
> denn in den I/O-Headern wird die letzte EEPROM-Adresse als E2END
> definiert.

Guter Einwand.
Werd das mal ins Turotial mit einbauen

> Die vorgeschlagene Lösung feste Adressen durch Anlegen eines Arrays
> maximaler Größe zu definieren, gefällt mir übrigens überhaupt nicht.

Da bist du nicht alleine :-)

> Das Layout muss komplett handgefrickelt werden. Ausserdem müssen alle
> Elemente vom gleichen Typ sein.

Nicht unbedingt.
Als ich das erste mal mit dem EEPROM gearbeitet habe, hab ich das so 
gemacht
#define   EE_DUMMY   0x000  // Dummyelement (Adresse 0 sollte nicht genutzt werden)
#define   EE_VALUE1  0x001  // Eine Bytevariable  
#define   EE_VALUE2  EE_VALUE1 + sizeof( unsigned char )  // unsigned char, weil EE_VALUE1 als Byte anzusehen ist
#define   EE_VALUE3  EE_VALUE2 + sizeof( double )         // weil EE_VALUE2 als double anzusehen ist

Schön ist das nicht. Übersichtlich ist das auch nicht und ja: es ist 
fehleranfällig und mann muss nach 2 Wochen höllisch aufpassen, dass bei 
jedem #define immer der sizeof des vorhergehenden Elements steht.

Ob ich jemanden diese Spielerei empfehlen würde: Definitiv nicht.

> Daher wäre es besser, die üblichen Nutzdaten in einer Struktur zu
> organisieren.

So habe ich das dann auch in weiteren Programmen gelöst. Diese Variante 
ist viel besser!

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Johann L. schrieb:
>> Die vorgeschlagene Lösung feste Adressen durch Anlegen eines Arrays
>> maximaler Größe zu definieren, gefällt mir übrigens überhaupt nicht.
>
> Da bist du nicht alleine :-)
>
>> Das Layout muss komplett handgefrickelt werden. Ausserdem müssen alle
>> Elemente vom gleichen Typ sein.
>
> Nicht unbedingt.
> Als ich das erste mal mit dem EEPROM gearbeitet habe, hab ich das so
> gemacht
>
> [...]
>
> Schön ist das nicht. Übersichtlich ist das auch nicht und ja: es ist
> fehleranfällig und mann muss nach 2 Wochen höllisch aufpassen, dass bei
> jedem #define immer der sizeof des vorhergehenden Elements steht.
>
> Ob ich jemanden diese Spielerei empfehlen würde: Definitiv nicht.

Ich frag mich immer, wer auf die Idee kommt, solche "Design Pattern" in 
ein Tutorial reinzuschreiben.

Jeder kann das gerne so machen, aber in ein Tutorial nach dem sich 
viele Einsteiger richten und dort Rat suchen, gehört das definitiv nicht 
rein!
Das ist meine bescheidene Meinung dazu.

Wenn Anfänger damit nicht zurechtkommen oder was nicht verstehen -- was 
keineswegs unwahrscheinlich ist -- und daraufhin im Forum nachfragen, 
bekommen sie erstmal eins auf die Mütze wegen ihres Codes ;-).

Absulute Adressen vergeben braucht ein Einsteiger schlichtweg nicht. 
Wenn er irgendwo ne CRC oder SERNUM reinhaben will, ist er definitiv 
kein Anfänger.

Zum Anzeigen/Ändern von Variableninhalten sind absolute Adressen voll 
daneben. Würdest du RAM-Daten an absolute Adressen forcieren, nur um sie 
in nem Debugger anzuzeigen?

Nein, natürlich nicht.

In jedem Debugger können Variablen und Objekte/Komposite/Arrays 
angezeigt werden, deren Adresse sowieso. Falls ein Debugger nicht 
schaffen sollte ein Objekt im EEPROM darzustellen, nimmt man die Adresse 
des Objekts, castes die (im Debugger natürlich) auf den Typ des Objekts 
und zeigt dann die Daten an.

Im Tutorial gibt es eineige stellen wo zweifelhafte Konstrukte verwendet 
oder sogar angepriesen werden. Leider.

Johann

Autor: Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich hatte demnächst vor, ein CAN-Netz mit ATMega88PA (evtl. auch 168; 
mal sehen, wieviel Flash ich verbrate zzgl. Reserve) aufzubauen. Da das 
gute Stück ja auch schon 512 byte EEPROM hat, der meistens ungenutzt 
ist, hatte ich folgende Vorstellung: Im Flash haben alle Nodes das 
gleiche Programm, was die Bootloader-Updates (die zweifellos kommen 
werden, und sei es nur als Bugfix oder Verbesserung) und das 
Programmieren der "Standardsoftware" stark vereinfacht. Die 
nodespezifischen Daten, wie z.B. ID, Masken, Filter, 
Kalibrierkonstanten/Reihenwiderstandswerte für KTY-Sensoren etc.) wollte 
ich ins EEPROM packen und dann nach dem jedem Reset quasi beim Booten 
einlesen. Also unterscheiden sich die Nodes nur durch die .eep-Datei. So 
wollte ich auch noch etwas Flash sparen. Aber das ganze klappt natürlich 
nur, wenn von festen Adressen aus dem EEPROM gelesen wird. Oder gibt es 
da womöglich bessere Methoden als die festen Adressen?

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In Prinzip kann man doch auf den ganzen Zauber verzichten.

Wenn man alle EEPRROM Variablen in einem File zusammenfasst, dann wird 
der Compiler die Reihenfolge im Speicher immer nach der Reihenfolge der 
Definitionen im Quellcode erzeugen.
Man darf dann halt nie die Reihenfolge ändern, sondern neue Variablen 
immer am Ende anfügen, dann bleiben die bisherigen an der selben, alten, 
festen Adresse.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Falser schrieb:
> In Prinzip kann man doch auf den ganzen Zauber verzichten.
>
> Wenn man alle EEPRROM Variablen in einem File zusammenfasst, dann wird
> der Compiler die Reihenfolge im Speicher immer nach der Reihenfolge der
> Definitionen im Quellcode erzeugen.
> Man darf dann halt nie die Reihenfolge ändern, sondern neue Variablen
> immer am Ende anfügen, dann bleiben die bisherigen an der selben, alten,
> festen Adresse.

Für eine professionelle Lösung würde ich was nicht machen und schon 
garnicht empfehlen das Layout von der Reihenfolge der Definitionen oder 
von der Linkreihenfolge (Anordnung im Makefile...) abhängig zu machen.

Man dat hier unterschiedliche Anwendungen (Firmware-Versionen) die von 
dem gleichen Linkzusammenhang bzw. Adresslage ausgehen.

Die Adresse kann man also vergeben wie oben SERNUM_ADDR oder man legt -- 
je nach Gusto -- das Ding in eine eigene (Input-)Section, bildet die per 
ld-Skript auf eine Output-Section, deren Adresse man dann festlegt, ab.

Johann

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> das Layout von der Reihenfolge der Definitionen oder
> von der Linkreihenfolge (Anordnung im Makefile...) abhängig zu machen.

Deshalb habe ich auch geschrieben, dass man alle EEPROM Variablen in 
EINEM Source-File zusammenfasst. Das ist für kleine Projekte, wie sie 
sich bei einem AVR ergeben, durchaus noch übersichtlich.
Der Compiler legt die Reihenfolge im Speicher nach der Reihenfolge der 
Definitionen fest, und der Linker macht gar nichts mehr, weil nur eine 
Section mit EEPROM Variablen existiert. Die Reihenfolge im Makefile ist 
somit komplett irrelevant.

Autor: Lutz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> Der Compiler legt die Reihenfolge im Speicher nach der Reihenfolge der
>> Definitionen fest, und der Linker macht gar nichts mehr, weil nur eine
>> Section mit EEPROM Variablen existiert.

Kann man sich darauf auch in Zukunft halbwegs sicher verlassen oder kann 
das bei einer der nächsten AVR-GCC-Versionen wieder ganz anders 
aussehen, weil "das ja ganz bestimmt nichts ist, womit irgendjemand 
plant". Die Frage aber wirklich rein auf AVR bezogen, wobei der GCC 
natürlich so ziemlich alles bedient und auf das "Abfallprodukt" AVR bei 
solchen Entscheidungen wohl wirklich keine Rücksicht genommen werden 
dürfte.

>> man legt -- je nach Gusto -- das Ding in eine eigene (Input-)Section,
>> bildet die per ld-Skript auf eine Output-Section, deren Adresse man
>> festlegt, ab.

Gesundheit! Nun aber ernsthaft: Kannst Du mir ein paar Google-Stichworte 
nennen, wie ich das Thema mal etwas nachlesen kann? Als ich damals das 
Lernen beim AVR (mit Assembler natürlich, aber nur das Nötigste) anfing, 
gab es irgendwie Schlüsselwörter, mit denen man etwas in bestimmte 
Sections/Codesegmente schreiben konnte, aber nun nutze ich eigentlich 
nur noch (ganz fröhlich) C. Ein Link wäre natürlich das Allerbeste.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lutz schrieb:
>>> Der Compiler legt die Reihenfolge im Speicher nach der Reihenfolge der
>>> Definitionen fest, und der Linker macht gar nichts mehr, weil nur eine
>>> Section mit EEPROM Variablen existiert.
> Kann man sich darauf auch in Zukunft halbwegs sicher verlassen oder kann
> das bei einer der nächsten AVR-GCC-Versionen wieder ganz anders
> aussehen, weil "das ja ganz bestimmt nichts ist, womit irgendjemand
> plant".

Zunächst mal legt GCC die Reihenfolge im Speicher überhaupt nicht fest. 
Das macht der Linker/Lokator und nicht der Compiler. Zumindest läuft das 
bei den GNU-Tools so. Möglich daß das bei anderen Compilern die keine 
Objekte erzeugen anders läuft, zB tcc.

> Die Frage aber wirklich rein auf AVR bezogen, wobei der GCC
> natürlich so ziemlich alles bedient und auf das "Abfallprodukt" AVR bei
> solchen Entscheidungen wohl wirklich keine Rücksicht genommen werden
> dürfte.

Das ist eine GCC-Angelegenheit, keine Sache des Backends. GCC kann 
Symboldefinitionen/Funktionen umsortieren, zB mit -funit-at-a-time 
(Standard bei optimize >= -O1). Dann hilft wohl -fno-toplevel-reorder, 
aber das sollte man aus dem einfachen Grund nicht verwenden, weil es 
schlichtweg unnötig ist und das was zu tun ist mit Bordmitteln klar und 
unmissverständlich ausgedrückt werden kann.

>>> man legt -- je nach Gusto -- das Ding in eine eigene (Input-)Section,
>>> bildet die per ld-Skript auf eine Output-Section, deren Adresse man
>>> festlegt, ab.
> Gesundheit! Nun aber ernsthaft:

Das ist mein Ernst :-)

Für komplexere µC braucht's eben ld-Skripte und ich wüsste jetzt keinen 
Kunden (nicht AVR sondern ein 32-Bitter) der nicht seine eigenen 
Linkerskripte hat. Der Vorteil davon ist, daß an einem zentralen Ort die 
Adresslage angegeben wird. Ausserdem ist wünschenswert, die Adresslage 
möglichst spät zu machen. Wird die Adresslage im Compiler per (foo_t*) 
0x1234 oder im Assembler festgelegt, muss für jede Adresslage neu 
generiert werden.

Bei Verwendung von ld-Skript kann man aber Objekte/Libs nehmen und 
braucht nur nen Linklauf zu machen und so zB für unterschiedliche 
RAM-Konfigurationen die Anwendung bauen ohne neu zu übersetzen. 
(Passiert zB mit dem Code unter der Motorhaube deines PKW so). Neu zu 
übersetzen ist nicht nur ne Zeitfrage sondern auch ne Kostenfrage. Wenn 
das Zeug neu zertifiziert werden muss (TÜV, SIL, ...) wird sowas richtig 
teuer.

> Als ich damals das
> Lernen beim AVR (mit Assembler natürlich, aber nur das Nötigste) anfing,
> gab es irgendwie Schlüsselwörter, mit denen man etwas in bestimmte
> Sections/Codesegmente schreiben konnte, aber nun nutze ich eigentlich
> nur noch (ganz fröhlich) C.

In C geht das eben nicht -- es sei denn über harte Adressvergabe und 
Cast.

Es wird also realisiert über Nicht-Standard-Erweiterungen, zB 
__attribute__((section)) in gcc bzw. mehr oder weniger gleichbedeutend 
damit Section-Pragmas. Dazu gehören auch PROGMEM, EEMEM, PSTR, ... die 
die Codeablage regeln (.eeprom, .pgmspace, .bootloader, ...)

> Ein Link wäre natürlich das Allerbeste.

http://sourceware.org/binutils/docs-2.19/ld/Scripts.html

Johann

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Zunächst mal legt GCC die Reihenfolge im Speicher überhaupt nicht fest.
> Das macht der Linker/Lokator und nicht der Compiler. Zumindest läuft das
> bei den GNU-Tools so. Möglich daß das bei anderen Compilern die keine
> Objekte erzeugen anders läuft, zB tcc.

Das sehe ich nicht so.
Der Compiler erzeugt verschiedene memory sections für code, 
initialisierte Daten, EEPROM usw.
Das Layout innerhalb dieser Sections, also z.B. die Reihenfolge der 
Variablen wird vom Compiler festgelegt.
Der Linker ordnet dann die Sections nach type und fasst diese zusammen, 
kann und darf aber nicht den Inhalt (Layout) innerhalb der Sections 
ändern.
Dieses Zusammenfassen ist IMHO auch nicht willkürlich, sondern ist von 
der Reihenfolge bestimmt, mit der die einzelnen Objects an den Linker 
übergeben werden.

Wenn also im Projekt nur eine Section für EEPROM Daten erzeugt wird, 
weil nur ein einziges File EEPROM Variablen deklariert, dann ist die 
Reihenfolge der EEPROM Variablen komplett vom Compiler bestimmt.
Ob jetzt der Compiler 100% verpflichtet ist, die Variablen im Speicher 
nach der Reihenfolge der Definitionen anzuordnen, wissen die Compiler 
Gurus, ich bin jedenfalls der Überzeugung, dass er es macht.
Es gibt meiner Meinung ja auch keinen Grund dies nicht zu tun. Der 
Compiler arbeitet das Quellfile sequentiell ab (OK, er macht mehrere 
Passes), und reserviert Speicher im den ensprechenden Sections 
sequentiell mit jeder Variable die er trifft.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Falser schrieb:
>> Zunächst mal legt GCC die Reihenfolge im Speicher überhaupt nicht fest.
>> Das macht der Linker/Lokator und nicht der Compiler. Zumindest läuft das
>> bei den GNU-Tools so. Möglich daß das bei anderen Compilern die keine
>> Objekte erzeugen anders läuft, zB tcc.
>
> Das sehe ich nicht so.
> Der Compiler erzeugt verschiedene memory sections für code,
> initialisierte Daten, EEPROM usw.
> Das Layout innerhalb dieser Sections, also z.B. die Reihenfolge der
> Variablen wird vom Compiler festgelegt.

Die Reihenfolge, in der der Compiler die Definitionen ausgibt, ist nicht 
festgelegt.

> Ob jetzt der Compiler 100% verpflichtet ist, die Variablen im Speicher
> nach der Reihenfolge der Definitionen anzuordnen, wissen die Compiler
> Gurus, ich bin jedenfalls der Überzeugung, dass er es macht.

Dieses Wissen ist auch Normalsterblichen zugänglich -- sofern sie 
gewillt sind die Dokumentation zu lesen, zB die der gcc-Schalter wie 
-funit-at-a-time.

> Es gibt meiner Meinung ja auch keinen Grund dies nicht zu tun.

Der Compiler kann die Symbole sammeln und erst am Ende des Compilelaufs 
ausgeben -- oder eben nicht wenn die nicht referenziert werden 
(-fwhole-program). Die Ausgabe kann aus einer Hashtabelle erfolgen, wird 
also idR unsortiert sein.

Und wenn der Compiler die Symbole so ausgibt wie sie in der Quelle 
stehen, dann ist das immer noch unspezifiziert . Sich darauf zu 
verlassen ist Hack und ich hatte schon mehr als ein "Bugreport" von 
Kunden genau wegen solchem Rumgefrickel (oder "Compilerfehler" wie das 
i=i++ nicht funktioniert oder Inline Assembler falchen Code generiert): 
Alles geht jahrelang prima und plötzlich, just vor Produktfertigstellung 
fliegt den Jungs alles um die Ohren. Natürlich im Feld.

Johann

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Falser schrieb:

> Ob jetzt der Compiler 100% verpflichtet ist, die Variablen im Speicher
> nach der Reihenfolge der Definitionen anzuordnen, wissen die Compiler
> Gurus, ich bin jedenfalls der Überzeugung, dass er es macht.

Meines Wissens ist das nicht vorgschreiben.
Lediglich wenn man die Vars in einer struct sammelt, muss die 
Reihenfolge der Strukturmember erhalten bleiben.
Aber freie Variablen darf der Compile im Speicher verteilen, wie es ihm 
am besten passt. Das kann auch Sinn machen, wenn der Compiler Speicher 
sparen muss und gleichzeitig Alignment Restriktionen einhalten will.

Sagen wir der Einfachheit halber, dass man auf einen Word nur dann 
zugreifen kann, wenn er im Speicher auf einer geraden Adresse liegt.
unsigned char i;
int j;
unsigned char k;
int l;

benutzt der Compiler die Reihenfolge direkt so wie sie im Code angegeben 
ist, müsste er zwischen dem unsigned char und dem int jeweils ein 
Padding Byte einbauen: in Summe werden daher 8 Byte verbraucht

dreht er die Reihenfolge im Speicher aber um
unsigned char i;
unsigned char k;
int j;
int l;

kommt er mit 6 Bytes Speicherverbrauch über die Runden.

Beim AVR spielt das natürlich keine Rolle, aber es geht ja ums 
alllgemeinere Prinzip. Und bei solchen Sachen ist der C-Standard 
praktisch immer so formuliert, dass man die Details dem Compiler 
überlässt und ihn möglichst wenig einschränkt oder auf etwas festnagelt.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich benutze überhaupt keine EEPROM-Variablen.

Ich lege alle Variablen, die gesichert werden sollen, als Struktur im 
SRAM an.
Und bei Bedarf lese ich diese vom EEPROM ein bzw. schreibe sie in den 
EEPROM.
Und dieser Funktion übergebe ich dann ganz einfach die 
EEPROM-Startadresse als int.

Eine default-Initialiserung dieser Struktur im SRAM bei leerem EEPROM 
erfolgt dann ganz normal vom Compiler.

Ein leerer EEPROM kann z.B. durch eine CRC oder durch den Inhalt 0xFF 
festgestellt werden.


Peter

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gut, ich geb's zu. Ihr habt recht, man kann sich auf den Compiler nicht 
100% verlassen.
Es wird zwar zu 99 % funktionieren, aber richtig garantiert ist es doch 
nicht.
Es wäre für mich aber kein so großes Problem auch im professionellen 
Bereich, weil man beim testen einer neuen Version sofort sehen sollte, 
ob das Speicherlayout und die Werte von früher korrekt übernommen werden 
oder nicht.
Wer sowas erst im Field sieht, ist sowieso unprofessionell bei seinem 
Testablauf.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Falser schrieb:

> Es wäre für mich aber kein so großes Problem auch im professionellen
> Bereich, weil man beim testen einer neuen Version sofort sehen sollte,

jaja, Engineering und die Konjunktive... ;-)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.