Hallo,
ich habe schon einige kleinere Projekte mit AVR Mikrocontroller
umgesetzt. Jetzt arbeite ich gerade an einem Projekt, mit mehreren
Klassen, Verketteten Listen und auch Strings.
Ich treffe dabei immer wieder auf Probleme. Hauptsächlich betrifft dies
wohl die Gültigkeit von Pointern und Objekten. Es wäre schön, wenn mir
jemand die folgenden Fragen beantworten kann, bzw. wenn jemand eine gute
Erklärung kennt, mir diese schickt.
1. Erzeugung von Objekten
Ich habe eine mehrdimensionale, doppeltverkettete Liste mit Classes
umgesetzt. (nicht mit Structs).
Normalerweise würde ich jetzt eine Klassen mit dem new operator
erzeugen. Dies scheint mein Compiler aber nicht zu kennen?. Ich erzeuge
also ein Objekt mit
KlassenName Objekt* = &KlassenName();
Der Compiler gibt zwar die Warnung aus: "taking adress of temporary"
aber es geht. Wird aber vermutlich daran liegen, dass alle Objekte in
der void main() erzeugt werden.
Wenn ich ein Obejekt so erstelle:
KlassenName Objekt = KlassenName();
KlassenName ObjektPointer = &Objket;
ist die Fehlermeldung weg, aber die Objekte bzw. teile davon scheinen
überschrieben zu werden.
Warum ist es nicht möglich mit
KlassenName Objekt* = new KlassenName();?
2.Strings
In den Klassen habe ich einige strings bzw. const char* gespeichert.
Diese sind denke ich schon während des Compilierens fest gespeichert und
können später nicht verändert werden. Jetzt will ich diese auf einem LCD
Display ausgeben. Manchmal soll aber auch ein dynamischer string (char*)
ausgegeben werden. Dies Möglichst mit der selben Funktion. Da sind noch
einige Probleme aufgetreten. Auch oft mit Überschriebenen char*
pointern. Mehr Details zu meinen Problemen damit will ich jetzt nicht
Schildern. Vieleicht kennt ja jemand eine Gute Seite, wo alles erklärt
ist.
Danke schonmal
JRE schrieb:> KlassenName Objekt* = &KlassenName();>> Der Compiler gibt zwar die Warnung aus: "taking adress of temporary"> aber es geht. Wird aber vermutlich daran liegen, dass alle Objekte in> der void main() erzeugt werden.
Das sollte so nicht gehen - du erzeugst ein temporäres objekt auf dem
stack, nimmst seine adresse, und löschst es direkt wieder, behälst aber
eine adresse die auf ungültigen speicher zeigt. du brauchst:
1
KlassenNameObjekt*=newKlassenName();
Aber new und malloc sind auf kleinen Controllern wie den AVR's kaum
sinnvoll; da macht man meistens alles mit statischer Speicherverwaltung,
ohne new/malloc. Somit sind keine verketteten Listen etc. möglich.
JRE schrieb:> KlassenName Objekt = KlassenName();
So legst du eine temporäre Instanz auf dem Stack an, kopierst sie an
eine zweite Stelle auf dem Stack, und löschst das Original direkt
wieder. Mit
1
KlassenNameObjekt;
legst du einfach nur eine Instanz an, ohne kopiererei.
> KlassenName ObjektPointer = &Objket;
Das sollte einen Compilerfehler geben, da du implizit eine Adresse in
deinen Objekttyp umcastest.
JRE schrieb:> KlassenName Objekt* = new KlassenName();?
s.o. - in "normalem" C++ auf PC's geht das genau so, aber auf AVR's ist
dynamische Speicherverwaltung eher unüblich.
JRE schrieb:> Auch oft mit Überschriebenen char*> pointern.
const char* kann man nicht überschreiben (da es vermutlich vom Compiler
in den Programmspeicher/flash gelegt wird, und der ist readonly). Du
musst den const char* in den RAM kopieren (z.B. mit strcpy), dort kannst
du ihn auch überschreiben.
Danke für die Antwort.
Meine verkettete Liste muss nicht unbedingt dynamisch sein. Somit könnte
ich ja alle Objekte und Pointer so anlegen:
KlassenName Objekt;
KlassenName ObjektPointer* = &Objekt;
?
Im konkreten Fall stellt die verkettete Liste eine Menüstrucktur für ein
4 Zeiliges LCD dar. Manche Menüeinträge bestehen aus 4 Ebenen, Andere
nur aus 2 oder 3. In jeder Ebene können unterschiedlich viele Einträge
sein, die dann wieder eine unterschiedliche Anzahl an Unterebenen haben
können.
Mit KlassenName Objekt; lege ich also bereits eine Instanz an?
Wie übergebe ich dann dem Konstruktor die Parameter? KlassenName
Objekt()?
JRE schrieb:> KlassenName Objekt;> KlassenName ObjektPointer* = &Objekt;
Ja, so geht das.
JRE schrieb:> Mit KlassenName Objekt; lege ich also bereits eine Instanz an?
Ja.
> Wie übergebe ich dann dem Konstruktor die Parameter? KlassenName> Objekt()?
Genau:
KlassenName Objekt(1, 2, 3, "Mein Name ist Hase", ObjektPointer);
Und warum wird das alles zur Laufzeit gemacht?
Die Menüstruktur ist doch statisch bekannt, d.h. kann bereits vom
Compiler angelegt werden.
Oder wird der Anwender die Menüstruktur zur Laufzeit verändern /
erweitern?
Sieht irgendwie nach OOOverkill aus...
einen neuen Eintrag erstellen kann.
Außerdem kann ich mit
1
Hauptmenue->Untermenue->next();
einfach das nächste Untermenü aktivieren.
Vieleicht kann man das ja einfach alles anpassen, dass es statisch
erzeugt wird, aber ich glaube nicht, dass der Compiler all diese
Funktionen durchgeht.
Ich habe jetzt eine Möglichkeit gefunden mit der man die Operatoren
new() und delete() zur Verfügung hat.
Es werden aber auch die Objekte die mit
1
KlassenNammeObjekt*=newKlassenName();
erzeugt werden gelöscht.
Ich erziele das gewünschte Verhalten mit
1
KlassenNameObjekt*=&KlassenName();
Aber der Compiler Warnt eben "Taking adress of Temporary". Deswegen
möchte ich es so nicht verwenden.
JRE schrieb:> Ja das ist richtig. Ich könnte ein Statisches Menü machen. Aber es geht> so recht komfortabel, indem ich einfach mit> Hauptmenue->Untermenue->NeuerEintrag("Irgendwas");> einen neuen Eintrag erstellen kann.
Kann man schon machen, aber du solltest dir im Klaren darüber sein, daß
beim AVR in der Regel der RAM sehr knapp ist und dein Code recht
verschwenderisch damit umgeht.
JRE schrieb:> Wenn ich ein Obejekt so erstelle:>> KlassenName Objekt = KlassenName();> KlassenName ObjektPointer = &Objket;>> ist die Fehlermeldung weg, aber die Objekte bzw. teile davon scheinen> überschrieben zu werden.
Möglicherweise ist dein RAM schon übervoll. Dann beginnen Heap und
Stack, sich gegenseitig zu überschreiben.
JRE schrieb:> Ja das ist richtig. Ich könnte ein Statisches Menü machen.
Nicht 'könnte'.
Das ist das, was du tun willst!
> Aber es geht> so recht komfortabel, indem ich einfach mit>
einen neuen
> Eintrag erstellen kann.
Genau diese Variante willst du eben nicht
* weil du dazu dynamisch allokieren musst
* weil es statisch genausogut, wenn nicht sogar einfacher geht.
> Außerdem kann ich mit
1
Hauptmenue->Untermenue->next();
einfach
> das nächste Untermenü aktivieren.
Das kannst du auch im statischen Fall. An der Verpointerung ändert sich
ja nichts. Es geht nur um die Art und Weise, wie die Datenstruktur
aufgebaut wird.
> Ich erziele das gewünschte Verhalten mit
1
KlassenNameObjekt*=
2
>&KlassenName();
>> Aber der Compiler Warnt eben "Taking adress of Temporary". Deswegen> möchte ich es so nicht verwenden.
Das ist die falsche Begründung.
Du willst es nicht verwenden, weil es grundfalsch ist!
Dein 'funktioniert doch' gilt nicht. Denn es funktioniert eben nicht.
Nur weil dir nicht gleich alles abstürzt, heißt das noch lange nicht,
dass es korrekt ist.
JRE schrieb:> Ok ich will es nicht weil es irgendwann schief gehen wird. Ist klar.>> Kannst du mir bitte ein erklären, wie ich es am besten statisch anlege?
Wie sehen denn deine Klassen aus?
Im Idealfall hast du einen Constructor, der dir die Verknüpfung
einträgt.
Bei einer struct würde man das zb so machen
// die sind auch wieder untereinander verlinkt, haben aber auch die
20
// Verpointerungen auf den jeweils ersten Menüpunkt im zugehörigen
21
// Submenü
22
MenuEntryhelp("Help",NULL,&author);
23
MenuEntrywork("Work",&help,NULL);
24
MenuEntrymenu("File",&work,&Open);
Ist nur ein Beispiel. Es gibt viele Varianten, wie man sowas machen
kann.
Getreu dem Motto: verwendet werden kann nur, was bekannt ist, muss man
die Menüstruktur von unten nach oben lesen. Das Menü beginnt also bei
'menu'.
"File" -> "Work" -> "Help"
| |
| v
| "Autor" -> "Version" -> "Copyright"
v
"Open" -> "Save" -> "Save As"
Ich habe nochmal drüber nachgedacht.
Also wenn ich das richtig sehe, lege ich bei einer statischen Lösung,
für jedes Objekt das ich brauche eine eigene richtige Variable an.
1
KlassenNameObjekt();
und dann nehme ich mir davon die Adressen und trage sie in die
Verkettete Liste ein. Ab dann ist ja alles so, wie mit der dynamischen
Lösung.
Seh ich das richtig?
Man kann es klar auch so deuten, dass eine Funktion (Objekt) mit
KlassenName als Rückgabewert deklariert werden soll.
Der Compiler erstellt aber offensichtlich ein Objekt. Mein Code funzt
jetzt. Ist aber doch eine Menge mehr Tipparbeit.
Danke an alle.
JRE schrieb:> Man kann es klar auch so deuten, dass eine Funktion (Objekt) mit> KlassenName als Rückgabewert deklariert werden soll.> Der Compiler erstellt aber offensichtlich ein Objekt.
Nein, definitiv nicht. Der Compiler hat nier nicht die Wahl, wie er
interpretiert, sondern muß eine Funktionsdeklaration draus machen.
> Mein Code funzt jetzt.
Auch wenn diesen nicht kenne, habe ich den Eindruck, daß das eher Zufall
ist.
:D lol danke.
Also ich habe es jetzt auch bemerkt. In meinem Code kamen keine
Konstruktoren ohne Parameter vor. Wenn ich dann daraus ein Objekt machen
erzeugen will geht das mit:
1
KlasseNameObjektName(Paramter)
Wenn ich aber ein Objekt aus eine Klasse mit Konstruktor ohne Parameter
erstellen will dann mit:
1
KlassenNameObjektName
Ansonsten wir wie oben doch richtig beschrieben versucht eine Funktion
anzulegen
Und "JRE kann nur Java" war auch nicht bös gemeint, sondern einmal durch
den NickName geradezu herausgefordert und dann auch nicht falsch, oder?
;-)
BTW, genau die Default-Konstruktor-"Falle" war vor 1..2 Wochen schon mal
dran.
JRE schrieb:> Der Compiler erstellt aber offensichtlich ein Objekt. Mein Code funzt> jetzt. Ist aber doch eine Menge mehr Tipparbeit.
Und damit er auch stabil läuft, solltest Du sehr vorsichtig mit dem
Anlegen von Objekten zur Laufzeit sein. Der Speicher könnte sonst
fragmentieren.
Beispiel: Du hast 1000 Bytes RAM
Du machst ein char* p1=malloc(400); ok, 600 Bytes frei
Du machst ein char* p2=malloc(200); ok, 400 Bytes frei
Du machst ein free(p1); ok, 800 Bytes frei
Du machst ein char *p3=malloc(600); meep! Fehler!
Du hättest zwar 600 freie Bytes, aber keine 600 zusammenhängenden
freien Bytes. Auf dem PC ist das nicht ganz so kritisch, weil Du da
virtuellen Speicher und eine MMU hast, aber in der Embedded-Welt ist so
etwas tödlich. Denke daran, dass Embedded-Systems jahrelang ohne
Neustart laufen sollen.
Deswegen: entweder alles statisch anlegen, oder dynamisch einmal beim
Systemstart und fortan nie wieder. Und dann auf die versteckten malloc()
und new achten.
Das ist nur eine der kleinen Gemeinheiten die Du Dir bewusst machen
musst.
C++ ist nicht in allen Fällen eine gute Idee.
fchk
Frank K. schrieb:> Du hättest zwar 600 freie Bytes, aber keine 600 zusammenhängenden> freien Bytes. Auf dem PC ist das nicht ganz so kritisch, weil Du da> virtuellen Speicher und eine MMU hast, aber in der Embedded-Welt ist so> etwas tödlich.
Virtueller Speicher und MMU helfen dabei auch nicht. Prozesse haben
trotzdem lineare Adressräume, wo man nicht einfach irgendwo mittendrin
was dazwischen einfügen kann.
Beim PC ist eher der Vorteil, daß das Verhältnis zwischen typischen
Größen der allokierten Blöcke und verfügbarem Speicher ein ganz anderes
ist. Das ermöglicht einerseits mehr Flexibilität bei der Suche der
freien Blöcke und andererseits auch Tricks wie reservierte Bereiche, wo
nur Objekte bestimmter besonders häufig auftretender Größe reinkommen,
die dann quasi wie Arrays verwaltet werden können.
> Denke daran, dass Embedded-Systems jahrelang ohne Neustart laufen sollen.
Bei sicherheitskritischen Embedded-Systemen ist dynamischer Speicher ja
oft komplett verboten.