Forum: Compiler & IDEs Spezielle GCC grundsatzfragen


von JRE (Gast)


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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
KlassenName Objekt* = new KlassenName();
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
KlassenName Objekt;
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.

von Dr. Sommer (Gast)


Lesenswert?

PS: Mit GCC hat das kaum was zu tun, das sind allgemeine C/C++ Sachen.

von JRE (Gast)


Lesenswert?

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()?

von Dr. Sommer (Gast)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von JRE (Gast)


Lesenswert?

Ja das ist richtig. Ich könnte ein Statisches Menü machen. Aber es geht 
so recht komfortabel, indem ich einfach mit
1
 Hauptmenue->Untermenue->NeuerEintrag("Irgendwas");
 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
 KlassenNamme Objekt* = new KlassenName();
 erzeugt werden gelöscht.

Ich erziele das gewünschte Verhalten mit
1
 KlassenName Objekt* = &KlassenName();

Aber der Compiler Warnt eben "Taking adress of Temporary". Deswegen 
möchte ich es so nicht verwenden.

von Rolf Magnus (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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
>
1
 Hauptmenue->Untermenue->NeuerEintrag("Irgendwas");
 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
 KlassenName Objekt* =
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.

von JRE (Gast)


Lesenswert?

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?

von C++ (Gast)


Lesenswert?

Problem ist halt, daß JRE nur Java kann und kein (embeded) C++ ;-)

von JRE (Gast)


Lesenswert?

Achso klar. Und da gibt es keine Pointer :D

von Karl H. (kbuchegg)


Lesenswert?

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
1
struct MenuEntry
2
{
3
  char *             text;
4
  struct MenuEntry * next;
5
  struct MenuEntry * subMenu;
6
}
7
8
// Alle Menüpunkte im "Help" Submenu
9
MenuEntry copyright ( "Copyright", NULL,       NULL );
10
MenuEntry version   ( "Version",   &copyright, NULL );
11
MenuEntry author    ( "Autor",     &version,   NULL );
12
13
// Alle Menüpunkte im "File" Submenu
14
MenuEntry saveAs    ( "Save As",   NULL,       NULL );
15
MenuEntry save      ( "Save",     &saveAs,     NULL );
16
MenuEntry open      ( "Open",     &save,       NULL );
17
18
// Und jetzt die Hauptmenüpunkte
19
// die sind auch wieder untereinander verlinkt, haben aber auch die
20
// Verpointerungen auf den jeweils ersten Menüpunkt im zugehörigen
21
// Submenü
22
MenuEntry help      ( "Help",      NULL,      &author );
23
MenuEntry work      ( "Work",      &help,      NULL );
24
MenuEntry menu      ( "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"

von JRE (Gast)


Lesenswert?

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
 KlassenName Objekt();

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?

von JRE (Gast)


Lesenswert?

Ah gleichzeitiger Post. Die Idee, den Konstruktor so zu verwenden ist 
gut. Danke sehr!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:
> struct MenuEntry
> {
>   char *             text;
>   struct MenuEntry * next;
>   struct MenuEntry * subMenu;
> }
>
> // Alle Menüpunkte im "Help" Submenu
> MenuEntry copyright ( "Copyright", NULL,       NULL );
> MenuEntry version   ( "Version",   &copyright, NULL );
> MenuEntry author    ( "Autor",     &version,   NULL );

Hier schiesst man sich mit C++ schnell mal selbst ins Knie wenn man 
anstatt dem POD MenuEntry "richtiges" C++ macht und eine echte Klasse 
nimmt:

Für non-POD Objekte wird dann doch ein Objekt zu Laufzeit angelegt — in 
einem impliziten, statischen Initializer der die nötigen Konstruktoren 
aufruft.

Das ist insbesondere auf AVR extrem unangenehm wenn man die Daten ins 
Flash legen will:  Im impliziten Initializer wird schreibend auf die 
Daten zugegriffen!

Und da C++ keine Obermenge von Embbedded-C ist, geht __flash auch 
nicht...

Beispiel: Man programmiert sich Fixed-Point Typen mit all den Sachen die 
Embbedded-C kennt mittels Overloading etc.

Lookup-Tabellen mit Fixed-Point Werten?  Pustekuchen und Fehlanzeige.

...also wieder zurück zu C...

p.e. wegen dem "Embbedded": Ansonsten gibt's ein schwachsinnigen 
auto-Link irgendwo hin.

von C++ (Gast)


Lesenswert?

JRE schrieb:
>
>   KlassenName Objekt();
>

das ist nur leider kein Aufruf des Default-Konstruktors, sondern die 
Deklaration einer Funktion.

von JRE (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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.

von JRE (Gast)


Lesenswert?

: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
 KlasseName ObjektName(Paramter)

Wenn ich aber ein Objekt aus eine Klasse mit Konstruktor ohne Parameter 
erstellen will dann mit:
1
 KlassenName ObjektName

Ansonsten wir wie oben doch richtig beschrieben versucht eine Funktion 
anzulegen

von C++ (Gast)


Lesenswert?

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.

von Frank K. (fchk)


Lesenswert?

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

von Rolf Magnus (Gast)


Lesenswert?

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.

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.