Forum: Compiler & IDEs Atmega16 Speicher bald voll Program / Data Unterschied ?


von technikus (Gast)


Lesenswert?

Hallo liebe Geminde,

nach Jahren schreibe ich, zugegeben, mit Halbwissen, mal wieder ein AVR 
Programm.
Ich nutze einen ATmega16 der meinen selbstbau Hifiverstärker steuern 
soll.
Letztendlich geht es mir aber auch um Übung und Spaß an der Sache, 
deswegen habe ich erfolgreich folgende Komponenten integriert:

- Infrarotempfang
- LCD Ansteuerung
- Drehencoderauswertung inkl. zusätzlicher Taster
- LCD Menu zur Einstellung von Eingangskanalnamen, Fernbedienungscodes, 
Displayhelligkeit usw.

Alles funktioniert bisher zu meiner vollen Zufriedenheit.
Jetzt spuckt der Compiler mir, im noch nicht fertigen Projekt, folgende 
Info aus:

Device: atmega16
    Program:    5568 bytes (34.0% Full)
    (.text + .data + .bootloader)
    Data:        848 bytes (82.8% Full)
    (.data + .bss + .noinit)
    EEPROM:       30 bytes (5.9% Full)

Program wird der Flash sein, Data der RAM speicher. Im Data bleibt jetzt 
nicht mehr viel :(

Wenn ich meine Menusteuerung rausschmeiße, sieht es so aus:

Device: atmega16
    Program:    2790 bytes (17.0% Full)
    (.text + .data + .bootloader)
    Data:         39 bytes (3.8% Full)
    (.data + .bss + .noinit)
    EEPROM:       30 bytes (5.9% Full)

Das Menu besteht aus 27 Einträgen (inkl. Untermenu) zum Auswählen / 
editieren der EEPROM Inhalte.

Wie man so ein Menu aufbaut habe ich mir hier abgelesen.
 Die Struktur der Menueinträge sieht so aus:
1
struct MenuEntry {
2
  char    Text[20];        //Text für Menupunkt
3
  MenuFnct  Function;        //Funktion für Menupunkt
4
  int    ArgumentToFunction;    //Parameter zur Funktionsübergabe
5
  uint8_t  Prev;          //vorheriger Menupunkt
6
  uint8_t  Next;          //nächster Menupunkt
7
  uint8_t  u_Prev;          //vorheriger Untermenupunkt
8
  uint8_t  u_Next;          //nächster Untermenupunkt
9
};

und sind für mein Verständnis im Data Speicher abgelegt. Ich würde die 
Geschichte deswegen im Flash ablegen wollen. //sofern das Sinn macht ???
Sofern ich das Verstanden habe, können die Daten aus dem Flash so nicht 
direkt gelesen werden.

Hier sind die Strukturvariablen angelegt:
1
struct MenuEntry MainMenu[] = {
2
 { "Input",    HandleInput,    0, 26, 7, 0, 1 },    //[0]  //Hauptmenu 1
3
 { "Input 1",  HandleInput1,    0, 6, 2, 1, 1  },    //[1]  //Untermenu 1.1
4
 { "Input 2",  HandleInput2,    0, 1, 3, 2, 2 },    //[2]
5
 { "Input 3",  HandleInput3,    0, 2, 4, 3, 3 },    //[3]
6
 { "Input 4",  HandleInput4,    0, 3, 5, 4, 4 },    //[4]
7
 { "Input 5",  HandleInput5,    0, 4, 6, 5, 5 },    //[5]
8
 { "Back",    HandleInputBack,  0, 5, 1, 0, 0 },    //[6]  //Untermenu 1.6
9
//usw.


Ich greife z.B. an folgenden Stellen auf die Strukturelemente zu:
1
actualMenuPoint=MainMenu[actualMenuPoint].u_Next;
2
3
//Funktion entsprechend Menueintrag aufrufen
4
MainMenu[actualMenuPoint].Function(MainMenu[actualMenuPoint].ArgumentToFunction );

und da wären noch die Fnktionen, die entsprechend dem Menupunkt 
ausgerufen werden
1
//Hauptmenu 1
2
void HandleInput( int arg )
3
{}
4
//usw.


Um meine Fragen noch einmal auf den Punkt zu bringen:
- Liege ich richtig, das ich den Speicherbedearf im data Speicher durch 
ablegen der Menustrukturen im Flash reduzieren (und dadurch die 
Programmspeicherbelegung erhöhen) kann ?

- Macht das überhaupt Sinn?
Viel Quellcode und flüchtige Variablen kommen nicht mehr dazu.

- Wenn ja, wie muss ich die Deklaration, die Definition und die Zugriffe 
auf die Strukurelemente der struct MenuEntry anpassen ?


Ich habe einiges über die Vorgehensweise gelesen, kriege das aber nicht 
auf meinen Quellcode umgebaut. Ehrlich gesagt fehlt da das Verständnis.


Gruß
technikus

von Oliver (Gast)


Lesenswert?

Vermutlich wird es schon eine ganze Menge bringen, wenn du erst einmal 
die konstanten Strings ins Flash verlegst.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Vereinfachung_f.C3.BCr_Zeichenketten_.28Strings.29_im_Flash

Alle Zugriffe auf diese Strings müssen dann halt umgeschrieben werden.

Die komplette Menustruktur ins Flash legen geht auch, dazu musst du aber 
alles so umschreiben, daß die Daten auch richtig aus dem Flash gelesen 
werden.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmspeicher_.28Flash.29

technikus schrieb:
> - Macht das überhaupt Sinn?
> Viel Quellcode und flüchtige Variablen kommen nicht mehr dazu.

Es kommt aber noch der Stack dazu, und damit wird es garantiert jetzt 
schon im SARM eng.

von technikus (Gast)


Lesenswert?

Hallo,

ich werde die Strings heute abend mal in den Flash verlagern.

>Es kommt aber noch der Stack dazu, und damit wird es garantiert jetzt
>schon im SARM eng.

Wie meinst du das ? Übernimmt der Compiler das für mich? Oder was muss 
ich tuen ?
Mein Halbwissen sagt mir, dass der Stack sich Programmzeiger bei 
Funktionsaufrufen merkt (bitte nicht schlagen ;) ).
Wie äussert sich ein Stack Problem ?

Gruß

von Peter D. (peda)


Lesenswert?

Oliver schrieb:
> Vermutlich wird es schon eine ganze Menge bringen, wenn du erst einmal
> die konstanten Strings ins Flash verlegst.

Das ist leider im AVR-GCC "a pain in the ass".
Das ganze wird total unleserlich, besonders die Initialisierung.

Du hast aber 20 Zeichen reserviert und die Texte scheinen deutlich 
kürzer (8 Byte) zu sein.
Dann kann man statt des Textes einen Pointer in das Array setzen.
Das braucht zwar 2 Bytes je Text mehr, aber die Texte belegen nur noch 
ihre wirkliche Größe. Bei wenigen langen Texten spart man dann.


Vermutlich ist der ATmega1284-Entwickler ein AVR-GCC Fan gewesen (16kB 
SRAM).
Der ist auch pinkompatibel zum ATmega16.


Peter

von Oliver (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Das ganze wird total unleserlich, besonders die Initialisierung.

Da wolltest du wohl schreiben:
Das ganze wird total unleserlich, bis auf die Initialisierung.

Die Strings in ein PSTR() einzupacken, macht ja nun nicht viel aus. Viel 
unleserlicher ist es, die hinterher wieder ais dem Flash 
"herauszupulen".

Peter Dannegger schrieb:
> Vermutlich ist der ATmega1284-Entwickler ein AVR-GCC Fan gewesen (16kB
> SRAM).
> Der ist auch pinkompatibel zum ATmega16.

Das wäre auch meine nächste Antwort gewesen ;)

technikus schrieb:
> Wie äussert sich ein Stack Problem ?

Sehr unschön. Der Stack überschreibt dir einfach die Variablen im RAM.

von technikus (Gast)


Lesenswert?

Hallo Peter,

da sieht man den Wald vor lauter Bäumen nicht mehr.
Ich habe jetzt 12 Zeichen pro String reserviert. Damit sind "nur" noch 
60% vom SRAM gefüllt.
Einen ATmega1284 werde ich wohl nicht brauchen ;)
Zur Not würde es ja auch ein ATmega32 tuen - aber wenn das nicht sein 
muss, erspare ich mir gerne den größeren Controller.

Gruß

von technikus (Gast)


Lesenswert?

Oliver schrieb:
> technikus schrieb:
>> Wie äussert sich ein Stack Problem ?
>
> Sehr unschön. Der Stack überschreibt dir einfach die Variablen im RAM.

und wie merke ich das ? Nur wenn es zu spät ist?

von technikus (Gast)


Lesenswert?

Hallo,

um den Thread abzuschließen:

Ich habe nun die Menutexte in den Flash abgelegt.

Das Compilerergebnis:

Device: atmega16
    Program:    5436 bytes (33.2% Full)
    (.text + .data + .bootloader)
    Data:        344 bytes (33.6% Full)
    (.data + .bss + .noinit)
    EEPROM:       30 bytes (5.9% Full)
    (.eeprom)

Also noch einmal die hälfte vom SRAM.
Sollte ich nocheinmal mehrere Strings ablegen, dann im Flash. Zugegeben, 
das Handling ist doch eher schei§€, doch die Ersparnis im SRAM ist es 
wert.


technikus

von Peter D. (peda)


Lesenswert?

Oliver schrieb:
> Die Strings in ein PSTR() einzupacken, macht ja nun nicht viel aus.

Also ich bin daran gescheitert.

Es ist ja nicht ein einfacher String, sondern eine Struct:

technikus schrieb:
> Hier sind die Strukturvariablen angelegt:
> struct MenuEntry MainMenu[] = {
>  { "Input",    HandleInput,    0, 26, 7, 0, 1 },    //[0]  //Hauptmenu 1
>  { "Input 1",  HandleInput1,    0, 6, 2, 1, 1  },    //[1]  //Untermenu 1.1
>  { "Input 2",  HandleInput2,    0, 1, 3, 2, 2 },    //[2]
>  { "Input 3",  HandleInput3,    0, 2, 4, 3, 3 },    //[3]
>  { "Input 4",  HandleInput4,    0, 3, 5, 4, 4 },    //[4]
>  { "Input 5",  HandleInput5,    0, 4, 6, 5, 5 },    //[5]
>  { "Back",    HandleInputBack,  0, 5, 1, 0, 0 },    //[6]  //Untermenu 1.6
> //usw.

Kannst mir aber gerne zeigen, wie man diese Struct definieren muß, damit 
sie im Flash landet.


Peter

von (prx) A. K. (prx)


Lesenswert?

Das geht nicht?
  struct MenuEntry MainMenu[] PROGMEM = { ...

PSTR bringt hier natürlich nichts, ist aber auch nicht nötig, weil 
char[] statt char*.

von Ralf (Gast)


Lesenswert?

A. K. schrieb:
> Das geht nicht?
>   struct MenuEntry MainMenu[] PROGMEM = { ...
Ist 'HandleInputXX' eine Konstante?

von (prx) A. K. (prx)


Lesenswert?

Ralf schrieb:

> Ist 'HandleInputXX' eine Konstante?

Ja, die Adresse einer Funktion ist konstant und als Initializer 
geeignet. Und wenn sie als Initializer für statische Daten im RAM 
geeignet ist, dann auch für Daten im ROM (in C, nicht C++) - dem 
Compiler ist dieser subtile Unterschied nämlich völlig egal.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

technikus schrieb:
> Ich habe jetzt 12 Zeichen pro String reserviert.

Lass das. Pack in die Struktur nur einen Pointer.
1
struct MenuEntry {
2
  char    *Text;        //Text für Menupunkt
3
  MenuFnct  Function;        //Funktion für Menupunkt
4
  int    ArgumentToFunction;    //Parameter zur Funktionsübergabe
5
  uint8_t  Prev;          //vorheriger Menupunkt
6
  uint8_t  Next;          //nächster Menupunkt
7
  uint8_t  u_Prev;          //vorheriger Untermenupunkt
8
  uint8_t  u_Next;          //nächster Untermenupunkt
9
};

Die Initialisierung davon sieht genauso aus wie bisher:
1
struct MenuEntry MainMenu[] = {
2
 { "Input",    HandleInput,    0, 26, 7, 0, 1 },    //[0]  //Hauptmenu 1
3
 { "Input 1",  HandleInput1,    0, 6, 2, 1, 1  },    //[1]  //Untermenu 1.1
4
//usw.

Damit wird für jeden Text nur die tatsächlich benötigte Anzahl von 
Zeichen (inklusive abschließender Null) reserviert, zuzüglich des 
Pointers.

Das ist gerade bei Strings unterschiedlicher Länge sehr ratsam, auch 
musst Du Dir nicht an zwei Stellen Gedanken über die maximal zulässige 
Stringlänge machen.

Das ganze kannst Du dann noch auf die im Flash gespeicherte Variante 
anpassen, auch da kannst Du statt des Strings selbst in der Struktur mit 
Pointern arbeiten.

Hier zeigen sich die Nachteile der Harvard-Architektur der AVRs, bei 
Controllern mit von-Neumann-Architektur ist das erheblich einfacher.

von (prx) A. K. (prx)


Lesenswert?

Rufus Τ. Firefly schrieb:

> Damit wird für jeden Text nur die tatsächlich benötigte Anzahl von
> Zeichen (inklusive abschließender Null) reserviert, zuzüglich des
> Pointers.

Und diese Strings kann man relativ leicht ins Flash packen, ohne sich 
mit Horden von pgm_xxx() Funktionen für jeden Strukturzugriff abkämpfen 
zu müssen. Jedenfalls in C - PSTR inmitten der Initialisierung ging 
zumindest in der damaligen avr-gcc Version nur in C, nicht aber in C++.

von technikus (Gast)


Lesenswert?

Ich habe es folgendermaßen gelöst
1
//Menutexte im Flash
2
//MainMenu100 = 1.00 / 213 = 2.13
3
const char MainMenu100[] PROGMEM = "Input";
4
const char MainMenu101[] PROGMEM = "Input 1";
5
const char MainMenu102[] PROGMEM = "Input 2";
6
const char MainMenu103[] PROGMEM = "Input 3";
7
const char MainMenu104[] PROGMEM = "Input 4";
8
const char MainMenu105[] PROGMEM = "Input 5";
9
const char MainMenu106[] PROGMEM = "Back";
10
11
const char MainMenu200[] PROGMEM = "Infrarot";
12
//usw.
13
14
//Array von Menutexten zusammenbauen 
15
const char *MainMenuStrArray[] PROGMEM = {
16
  MainMenu100,
17
  MainMenu101,
18
  MainMenu102,
19
  MainMenu103,
20
  MainMenu104,
21
  MainMenu105,
22
  MainMenu106,
23
  
24
  MainMenu200
25
//usw.
26
27
28
typedef void (*MenuFnct)( int);
29
30
struct MenuEntry {
31
  const char *Text;            //Text für Menupunkt
32
  MenuFnct  Function;        //Funktion für Menupunkt
33
  int    ArgumentToFunction;    //Parameter zur Funktionsübergabe
34
  uint8_t  Prev;          //vorheriger Menupunkt
35
  uint8_t  Next;          //nächster Menupunkt
36
  uint8_t  u_Prev;          //vorheriger Untermenupunkt
37
  uint8_t  u_Next;          //nächster Untermenupunkt
38
};
39
40
41
//Struktur von Menueinträgen
42
struct MenuEntry MainMenu[] = {
43
 { MainMenu100,    HandleInput,        0, 26, 7, 0, 1},    //[0]  //Hauptmenu 1
44
 { MainMenu101,    HandleInput1,        0, 6, 2, 1, 1  },    //[1]  //Untermenu 1.1
45
 { MainMenu102,    HandleInput2,        0, 1, 3, 2, 2 },    //[2]
46
 { MainMenu103,    HandleInput3,        0, 2, 4, 3, 3 },    //[3]
47
 { MainMenu104,    HandleInput4,        0, 3, 5, 4, 4 },    //[4]
48
 { MainMenu105,    HandleInput5,        0, 4, 6, 5, 5 },    //[5]
49
 { MainMenu106,    HandleInputBack,      0, 5, 1, 0, 0 },    //[6]  //Untermenu 1.6
50
        
51
 { MainMenu200,    HandleInfrarot,        0, 0, 20, 0, 8 }    //[7]  //Hauptmenu 2
52
//usw.
53
54
55
//Funktionen für Menupunkte
56
57
//Hauptmenu 1
58
void HandleInput( int arg )
59
{}
60
//usw.
61
62
//Funktion entsprechend Menueintrag aufrufen
63
    MainMenu[actualMenuPoint].Function( MainMenu[actualMenuPoint].ArgumentToFunction );
64
65
//Menu zurück
66
actualMenuPoint=MainMenu[actualMenuPoint].Prev;
67
//vor usw.
68
69
lcd_puts_p(( pgm_read_word( &MainMenu[actualMenuPoint].Text ) ));  //Menutext aus Flash holen


Alles andere ist so geblieben, da ich nur die Menutexte in den Flash 
gelegt habe.

Ich danke euch vielmals für die Unterstützung!

Eine Frage ist aber noch offen: Wenn der SRAM zu voll wird und nicht 
mehr genügend Platz für den Stack bleibt, was passiert?
Merke ich das erst bei einem Crash im Programm, und wieviel Platz sollte 
ich für den Stack im SRAM lassen?
Klar, das wird programmabhängig sein, es wird aber doch eine 
Schmerzgrenze geben.


Gruß

von technikus (Gast)


Lesenswert?

technikus schrieb:
> //Array von Menutexten zusammenbauen
> const char *MainMenuStrArray[] PROGMEM = {
>   MainMenu100,
>   MainMenu101,
>   MainMenu102,
>   MainMenu103,
>   MainMenu104,
>   MainMenu105,
>   MainMenu106,
>
>   MainMenu200
> //usw.

Kann man sich hier natürlich sparen ;)

von technikus (Gast)


Lesenswert?

!ACHTUNG! Meine Lösung klappt so nicht wie dargestellt.
Ich habe die ganze Zeit den alten Hexfile geladen ;)

Die Texte aus dem Flash werden nicht im Display ausgegeben.

Wenn ich ein Array der Strings anlege kann ich mit der lcd_puts_p() 
Funktion die Einträge auf das Display schreiben.
1
//Array von Menutexten zusammenbauen
2
> const char *MainMenuStrArray[] PROGMEM = {
3
>   MainMenu100,
4
>   MainMenu101,
5
>   MainMenu102,
6
>   MainMenu103,
7
>   MainMenu104,
8
>   MainMenu105,
9
>   MainMenu106,
10
>
11
>   MainMenu200
12
13
14
lcd_puts_p((const char*)(pgm_read_word((MainMenuStrArray[actualMenuPoint])) )); //funktioniert

Erkennt jemand den Fehler im Quelltext vom Posting oben ?

von Karl H. (kbuchegg)


Lesenswert?

technikus schrieb:

> Wenn ich ein Array der Strings anlege kann ich mit der lcd_puts_p()
> Funktion die Einträge auf das Display schreiben.
>
1
> //Array von Menutexten zusammenbauen
2
>> const char *MainMenuStrArray[] PROGMEM = {
3
>>   MainMenu100,
4
>>   MainMenu101,
5
>>   MainMenu102,
6
>>   MainMenu103,
7
>>   MainMenu104,
8
>>   MainMenu105,
9
>>   MainMenu106,
10
>>
11
>>   MainMenu200
12
> 
13
> 
14
> lcd_puts_p((const
15
> char*)(pgm_read_word((MainMenuStrArray[actualMenuPoint])) ));
16
> //funktioniert
17
>

das glaub ich nicht.
MeinMenuStrArray liegt selber wieder im Flash. Also musst du der 
pgm_read_word Funktion die Adresse das Strings mit dem Index 
actualMenuPoint im Array MainMenuStrArray geben. Die Adresse! Nicht den 
Wert!

Wenn du's nicht mehr durchblickst, dann nimm Abstand vor zu tiefen 
Schachtelungen und drösle dir die Dinge in mehrere Anweisungen und 
Hilfsvariablen auf (der Compiler schmeisst die eh wieder raus)
1
  const char* adrOfStringInFlash = pgm_read_word( &MainMenuStrArray[actualMenuPoint] );
2
  lcd_puts_p( adrOfStringInFlash );

>
> Erkennt jemand den Fehler im Quelltext vom Posting oben ?

Auf welchen Code beziehst du dich?

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:

>> Erkennt jemand den Fehler im Quelltext vom Posting oben ?
>
> Auf welchen Code beziehst du dich?


Wahrscheinlich auf das hier
1
struct MenuEntry MainMenu[] = {
2
 { MainMenu100,    HandleInput,        0, 26, 7, 0, 1},    //[0]  //Hauptmenu 1
3
4
....
5
lcd_puts_p(( pgm_read_word( &MainMenu[actualMenuPoint].Text ) ));  //Menutext aus Flash holen

MainMenu liegt nicht im Flash. Also brauchst du auch kein pgm_read_word 
um die Inhalte aus MainMenu auszulesen.

  lcd_puts_p( MainMenu[actualMenuPoint].Text );

wieder aufgedröselt
1
  const char* adrOfStringInFlash = MainMenu[actualMenuPoint].Text;
2
  lcd_puts_p( adrOfStringInFlash );

Da MainMenu nicht als im Flash liegend markiert ist, kann ganz normal 
auf das Array zugegriffen und daraus gelesen werden.

von technikus (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> das glaub ich nicht.
> MeinMenuStrArray liegt selber wieder im Flash. Also musst du der
> pgm_read_word Funktion die Adresse das Strings mit dem Index
> actualMenuPoint im Array MainMenuStrArray geben. Die Adresse! Nicht den
> Wert!

Zu Spät am Abend ;) Als verzweifelter Laie ist mir im Posting das & 
durchgerutscht.
Wenn ich die Adresse an die Funktion Pgm_read_word Funktion 
übergebe,passt es.

Gruß

von technikus (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Karl Heinz Buchegger schrieb:
>
>>> Erkennt jemand den Fehler im Quelltext vom Posting oben ?
>>
>> Auf welchen Code beziehst du dich?
>
>
> Wahrscheinlich auf das hier
> struct MenuEntry MainMenu[] = {
>  { MainMenu100,    HandleInput,        0, 26, 7, 0, 1},    //[0]  //Hauptmenu 1
>
> ....
> lcd_puts_p(( pgm_read_word( &MainMenu[actualMenuPoint].Text ) ));  //Menutext 
aus Flash holen
>
> MainMenu liegt nicht im Flash. Also brauchst du auch kein pgm_read_word
> um die Inhalte aus MainMenu auszulesen.
>
>   lcd_puts_p( MainMenu[actualMenuPoint].Text );
>
> wieder aufgedröselt
>   const char* adrOfStringInFlash = MainMenu[actualMenuPoint].Text;
>   lcd_puts_p( adrOfStringInFlash );


Genau den Teil meinte ich. Da aber das Strukturelement MainMenu100 im 
Flash liegt und Teil von MainMenu (was nicht im Flash liegt) ist, dachte 
ich, dass ich mit pgm_read_word auf das im Flash liegende Element 
zugreifen muss.

Probiere ich heute Abend.

Danke
>
> Da MainMenu nicht als im Flash liegend markiert ist, kann ganz normal
> auf das Array zugegriffen und daraus gelesen werden.

von Karl H. (kbuchegg)


Lesenswert?

technikus schrieb:

> Genau den Teil meinte ich. Da aber das Strukturelement MainMenu100 im
> Flash liegt und Teil von MainMenu (was nicht im Flash liegt) ist,


das ist an dieser Stelle uninteressant.

Du greifst zuallererst auf MainMenu zu. Und das liegt nun mal nicht im 
Flash.
Das du aus dem MainMenu dann einen Pointer bekommst, der auf einen 
String zeigt, der dann im Flash liegt ist eine ganz andere Geschichte. 
Aber darum kümmert sich ja das lcd_puts_p.

Eine Analogie:

Dein MainMenu ist ein Inhaltsverzeichnis auf dem vermerkt ist, welche 
Bücher es gibt. Das Inhaltsverzeichnis liegt im Foyer auf, jeder kann es 
ohne weiteres lesen und sich aus diesem Inhaltsverzeichnis herauslesen, 
in welchem Regal das Buch zu finden ist. Nur wenn du an die eigentlichen 
Bücher heran willst, dann brauchst du einen Wachmann, der den Tresor 
aufschliest, damit du an die Bücher rankommst.

Legst du das Inhaltsverzeichnis selber auch in den Tresor, dann brauchst 
du auch zum Lesen des Inhaltsverzeichnisses schon den Wachmann zum 
aufschliessen.

von technikus (Gast)


Lesenswert?

Verstanden !

Danke

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.