Forum: Compiler & IDEs C++ auf AVR: new und virtuelle Methoden


von Detlev T. (detlevt)


Lesenswert?

Hallo Leute,

ich habe hier ( Beitrag "c++ new und delete" ) eine 
Methode gefunden, wie man den ab Werk nicht vorhandenen new-Operator für 
den GCC nachrüstet. Ich bin mir aber nicht sicher, ob diese Krücke auch 
dann noch funktioniert, wenn man Objekte mit virtuellen Methoden damit 
erzeugt.

Schon 'mal gemacht? Geht's?

Vielen Dank für eure Antworten.

Gruß, DetlevT

von Karl H. (kbuchegg)


Lesenswert?

Detlev T. schrieb:

> Schon 'mal gemacht? Geht's?

Was hindert dich daran, es auszuprobieren?

1
#include <avr/io.h>
2
3
class A
4
{
5
  public:
6
    virtual void doit()   { PORTB |= ( 1 << PB0); }
7
};
8
9
class B : public A
10
{
11
  public:
12
    virtual void doit()   { PORTB |= ( 1 << PB1 ); }
13
};
14
15
void foo( A* obj )
16
{
17
  obj->doit();
18
}
19
20
int main()
21
{
22
  DDRB = 0x03;
23
  PORTB = 0x00;
24
25
  B objB;
26
27
  foo( &objB );
28
29
  while( 1 )
30
    ;
31
}

wenn virtuelle Funktionen funktionieren, dann tut sich was am Pin PB1. 
Wenn nicht, dann am Pin PB0.

Es gibt Dinge, die hat man mit einem Testprogramm schneller geklärt, als 
man in einem Forum die entsprechende Frage eingetippt hat. Und dann weiß 
man es sogar ganz genau :-)

PS: was haben eigentlich virtuelle Funktionen damit zu tun, wie das 
Objekt erzeugt wurde? Alles was man braucht, ist ein Pointer aufs Objekt 
um virtuelle Aufrufe zu triggern. new hat damit erst mal genau gar 
nichts zu tun.

von Marco M. (marco_m)


Lesenswert?

Das ist ein Workaround, wenn man eben was portieren will. Das "richtige" 
new macht mehr. Es ruft new_handler auf, wenn Speicher alle ist, wirft 
bad_alloc() wenn danach keiner da ist usw. Dein Workaround gibt einen 
NULL Pointer zurück, wenn der Speicher alle ist. Das ist nicht erlaubt. 
Ausserdem brauchst du noch eine Implementation operator new[] und 
operator delete[].

Ohne Exceptions kannst du jedenfalls keinen standardkonformen operator 
new implementieren, sondern nur die Variante "operator new(std::size_t 
st,const std::nothrow_t &) throw();" - der im Fehlerfalle einen 
null-pointer zurückgibt.

"new" soll das Objekt nicht initialisieren, sondern nur den Speicher 
bereitstellen, virtuelle Funktionen gehen daher, weil die Strukturen vom 
Objektkonstruktor aufgebaut werden.

von Detlev T. (detlevt)


Lesenswert?

@Karl Heinz
Hast du die Frage überhaupt gelesen? Es geht um einen Würg-Around für 
den nicht vorhandenen new-Operator. Der taucht in deinem Listig ja gar 
nicht auf.

@Marco
Danke für deine Antwort. Das wollte ich wissen. Und jetzt verstehe ich 
sogar, warum dieser Würg-Around nicht standardmäßig bei avr-gcc dabei 
ist.

von Karl H. (kbuchegg)


Lesenswert?

Detlev T. schrieb:
> @Karl Heinz
> Hast du die Frage überhaupt gelesen? Es geht um einen Würg-Around für
> den nicht vorhandenen new-Operator. Der taucht in deinem Listig ja gar
> nicht auf.

Nö.
Es geht darum, ob virtuelle Funktionen funktionieren.
Lies doch deine Frage noch mal!

Und dann erklär mir mal, was virtuelle Funktionen mit new zu tun haben 
sollen!
Und selbst wenn, und ich betone "wenn", es da einen dubiosen 
Zusammenhang geben könnte (der nicht existiert, weil das eine mit dem 
anderen nichts zu tun hat), was hindert dich daran, aus
1
  B objB;
2
3
  foo( &objB );

ein
1
  B* objB = new B;
2
3
  foo( objB );

zu machen.

Richtig. Nichts.
Ausser das DU es tun müsstest.

von Detlev T. (detlevt)


Lesenswert?

@Karl Heinz
Nimm das bitte nicht persönlich! Es geht NICHT darum, ob virtuelle 
Methoden generell funktionieren, sondern nur ob sie mit Objekten 
funktionieren, die mit diesem nicht konformen new-Würgaround erzeugt 
wurden.

Ich kenne mich mit den Interna von C+-Compilern nicht aus. Wenn ich 
"new" schreibe, muss irgendeine Routine 1.) Speicherplatz reservieren, 
2.) Die Zeiger auf die virtuellen Methoden dort eintragen. Ob letzteres 
die Aufgabe von "new" ist, wusste ich bis vor kurzem nicht. In dem von 
mir angegebenen Thread wurden nur Objekte ohne virtuelle Methoden 
verwendet.

Natürlich kann man auch alles selbst ausprobieren, die Fehler suchen, 
die andere schon gesucht haben und am Ende vielleicht sogar die Lösungen 
finden, auf die andere schon längst gekommen sind. Ich finde es aber 
ganz gut, dass es mit diesem Forum auch einen anderen Weg gibt.

Gruß, DetlevT

von C++ (Gast)


Lesenswert?

C++ Objekte mit virtuellen Methoden haben doch (in irgend einer Form) 
eine Zeiger auf die konkrete Methoden-Tabelle (ein Array von 
Methoden-Addressen).
Diesen Ptr richtig zu initialisieren kann aber nur der Konstruktor der 
konkreten Klasse.
-> "::new()" kann das garnicht leisten.
-> wie (komfortabel,fehlersicher,...) auch immer man den Speicher 
allokiert,
   sobald man ihn hat, macht der GCC den Rest.

von C++ (Gast)


Lesenswert?

BTW: AVR-GCC legt diese Tabellen leider im RAM ab. Kein wirklich großes 
Problem, nur ist RAM auf AVR's immer knapp und leider nicht "const", 
d.h. kleine Schreibfehler auf diese Tabellen haben event. große 
Auswirkung auf den weiteren Programmablauf ;-)

von Marco M. (marco_m)


Lesenswert?

C++ schrieb:
> BTW: AVR-GCC legt diese Tabellen leider im RAM ab. Kein wirklich großes
> Problem, nur ist RAM auf AVR's immer knapp und leider nicht "const",
> d.h. kleine Schreibfehler auf diese Tabellen haben event. große
> Auswirkung auf den weiteren Programmablauf ;-)

Oh ja, das ist wichtig. Wenn man nämlich von einer Basisklasse mit 
virtuellen Membern ableitet, dann wird die komplette vtable der 
Basisklasse mindestens dupliziert. Auf diese Weise ist der SRAM schnell 
voll.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz Buchegger schrieb:

> wenn virtuelle Funktionen funktionieren, dann tut sich was am Pin PB1.
> Wenn nicht, dann am Pin PB0.

Mit den richtigen Optimierungen erzeugt avr-gcc für einen 
ATmega32 folgendes:
 
1
main:
2
  ldi r24,lo8(3)
3
  out 0x17,r24
4
  out 0x18,__zero_reg__
5
  sbi 0x18,1
6
.L4:
7
  rjmp .L4

Es funktioniert also.

von Carl D. (jcw2)


Lesenswert?

Was auch zeigt, wie "riesig" der C++ Overhead sein kann ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Carl Drexler schrieb:
> Was auch zeigt, wie "riesig" der C++ Overhead sein kann ;-)

Oder wie gut GCC sein kann, wenn man die richtigen Optionen wählt :-)

Für C++ gibt's aber auch genügend Beispiele, wie man ohne großen Auswand 
den Flash füllt oder gar den RAM.  Wieder sind virtuelle Methoden ein 
Beispiel, die manche Entwickler selbst auf 32-Bit Embedded-Systemen als 
obsolet betrachten...

von Carl D. (jcw2)


Lesenswert?

Johann L. schrieb:
> Oder wie gut GCC sein kann, wenn man die richtigen Optionen wählt :-)

Es wird aber oftmals behauptet, daß C++ immer riesig sein muß. Was, wie 
man sehen kann, nicht stimmt. Das wollte ich nur anmerken.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Carl Drexler schrieb:
> Johann L. schrieb:
>> Oder wie gut GCC sein kann, wenn man die richtigen Optionen wählt :-)
>
> Es wird aber oftmals behauptet, daß C++ immer riesig sein muß.

Naja, bei so einem trivialen Programm darf man von dieser Behauptung 
auch kleinere Abstriche machen.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:
> Karl Heinz Buchegger schrieb:
>
>> wenn virtuelle Funktionen funktionieren, dann tut sich was am Pin PB1.
>> Wenn nicht, dann am Pin PB0.
>
> Mit den richtigen Optimierungen erzeugt avr-gcc für einen
> ATmega32 folgendes:
>  
>
1
> main:
2
>   ldi r24,lo8(3)
3
>   out 0x17,r24
4
>   out 0x18,__zero_reg__
5
>   sbi 0x18,1
6
> .L4:
7
>   rjmp .L4
8
>
>
> Es funktioniert also.

:-)
Das ist fies. Ist ja alles geinlined worden :-)

von Rolf M. (rmagnus)


Lesenswert?

Detlev T. schrieb:
> Natürlich kann man auch alles selbst ausprobieren, die Fehler suchen,
> die andere schon gesucht haben und am Ende vielleicht sogar die Lösungen
> finden, auf die andere schon längst gekommen sind. Ich finde es aber ganz
> gut, dass es mit diesem Forum auch einen anderen Weg gibt.

Du sollst ja keinen Fusionsreaktor erfinden, sondern nur probieren, ob 
virtuelle Memberfunktionen gehen. Wenn ja, hast du deine Antwort 
innerhalb von 5 Minuten, wenn nein, kannst du immer noch im Forum 
nachfragen, warum es nicht geht und wie man das behebt.

von Roland H. (batchman)


Lesenswert?

Johann L. schrieb:
> Mit den richtigen Optimierungen erzeugt avr-gcc für einen
> ATmega32 folgendes:

Karl Heinz Buchegger schrieb:
> Das ist fies. Ist ja alles geinlined worden :-)

Oha. Johann, welche Optimierungen waren denn das bitte? Mit -03 bei GCC 
4.6.2 konnte ich das nicht nachvollziehen. Hat er die vtable komplett 
eliminiert?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Roland H. schrieb:
> Oha. Johann, welche Optimierungen waren denn das bitte?

-Os -fwhole-program genügt bei mir.

von Karl H. (kbuchegg)


Lesenswert?

Roland H. schrieb:

> Hat er die vtable komplett
> eliminiert?

Ja, hat er.
GCC hat rausgefunden, dass der dynamische Typ innerhalb foo() ein 'class 
B' ist, und er damit den Aufruf direkt und nicht virtual machen kann. 
Damit gibt es aber keine Referenz mehr auf die vtable und die kann dann 
wiederrum vom Linker entfernt bzw. gar nicht eingelinkt werden.

Und dabei wollte ich ihm alles so schön in einer Funktion verstecken, 
damit genau das nicht passiert :-)

von Roland H. (batchman)


Lesenswert?

Jörg Wunsch schrieb:
>> Oha. Johann, welche Optimierungen waren denn das bitte?
>
> -Os -fwhole-program genügt bei mir.

Bei mir immer noch kein Erfolg. Welcher GCC kam zum Einsatz?

Bei mir steht am Ende folgendes im .lss:
1
000000a6 <A::doit()>:
2
  a6:   28 9a           sbi     0x05, 0 ; 5
3
  a8:   08 95           ret
4
5
000000aa <B::doit()>:
6
  aa:   29 9a           sbi     0x05, 1 ; 5
7
  ac:   08 95           ret
8
9
000000ae <main>:
10
  ae:   cf 93           push    r28
11
  b0:   df 93           push    r29
12
  b2:   00 d0           rcall   .+0             ; 0xb4 <main+0x6>
13
  b4:   cd b7           in      r28, 0x3d       ; 61
14
  b6:   de b7           in      r29, 0x3e       ; 62
15
  b8:   83 e0           ldi     r24, 0x03       ; 3
16
  ba:   84 b9           out     0x04, r24       ; 4
17
  bc:   15 b8           out     0x05, r1        ; 5
18
  be:   84 e0           ldi     r24, 0x04       ; 4
19
  c0:   91 e0           ldi     r25, 0x01       ; 1
20
  c2:   9a 83           std     Y+2, r25        ; 0x02
21
  c4:   89 83           std     Y+1, r24        ; 0x01
22
  c6:   ce 01           movw    r24, r28
23
  c8:   01 96           adiw    r24, 0x01       ; 1
24
  ca:   0e 94 55 00     call    0xaa    ; 0xaa <B::doit()>
25
  ce:   ff cf           rjmp    .-2             ; 0xce <main+0x20>

Kompiliert mit
1
/home/gcc-avr-4.6.2/data/gcc-toolchains-precompiled/avr/avr8-gnu-toolchain-3.4.0.663/bin/avr-gcc -mmcu=atmega328p -DF_CPU=16000000ul -Wall -Os -fwhole-program -o build/demo-cplusplus-4.o -c ../../../../common/apps/demo-cplusplus-4/src/demo-cplusplus-4.cpp
2
3
/home/gcc-avr-4.6.2/data/gcc-toolchains-precompiled/avr/avr8-gnu-toolchain-3.4.0.663/bin/avr-gcc -mmcu=atmega328p -o build/demo-cplusplus-4-atmega328p-4.6.2.elf build/demo-cplusplus-4.o
4
5
/home/gcc-avr-4.6.2/data/gcc-toolchains-precompiled/avr/avr8-gnu-toolchain-3.4.0.663/bin/avr-gcc --version
6
avr-gcc (AVR_8_bit_GNU_Toolchain_3.4.0_663) 4.6.2

Karl Heinz Buchegger schrieb:
> Und dabei wollte ich ihm alles so schön in einer Funktion verstecken,
> damit genau das nicht passiert :-)

Tja, der GCC kann's keinem recht machen :-) Ich möchte, dass genau das 
bei mir passiert :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Roland H. schrieb:
>> -Os -fwhole-program genügt bei mir.
>
> Bei mir immer noch kein Erfolg. Welcher GCC kam zum Einsatz?

4.7.2

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Roland H. schrieb:

> Oha. Johann, welche Optimierungen waren denn das bitte?

avr-gcc 4.8 mit -flto -O2, sollte aber auch mit 4.7 gehen.

von Carl D. (jcw2)


Lesenswert?

AVR-gcc 4.8?
Darf man den schon richtig benutzen ;-)

von Rolf M. (rmagnus)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und dabei wollte ich ihm alles so schön in einer Funktion verstecken,
> damit genau das nicht passiert :-)

Dann mußt du der Funktion noch das Attribut noinline geben. Wäre doch 
gelacht, wenn wir dem nicht unseren Willen aufzwingen könnten.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Carl Drexler schrieb:
> avr-gcc 4.8?
> Darf man den schon richtig benutzen ;-)

Nein, den darf man nicht benutzen!  Außer ich, ich hab eine 
Sondererlaubnis.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:
> Carl Drexler schrieb:
>> avr-gcc 4.8?
>> Darf man den schon richtig benutzen ;-)
>
> Nein, den darf man nicht benutzen!  Außer ich, ich hab eine
> Sondererlaubnis.

"Mein Name ist Johann. Johann L.
... mit der Lizenz zum compilieren"

von Carl D. (jcw2)


Lesenswert?

meine Frage war durchaus ernst gemeint und bezog sich auf die für mich 
nicht leicht überblickbare Regression-Liste.
Naja, vielleicht nicht "darf man", eher "sollte man".

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wie gesagt, ich hatte gerade eine 4.8 zur Hand; mit der 4.7 geht das 
bestimmt genauso.

von Coder (Gast)


Lesenswert?

Juhu... die Makro-Funktionen sterben aus und der C Code kann MISRAtener 
werden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Was ist denn eine Makro-Funktion? Sowas wie eine Header-Bibliothek?

von Detlev T. (detlevt)


Lesenswert?

Johann L. schrieb:
> Was ist denn eine Makro-Funktion? Sowas wie eine Header-Bibliothek?

So etwas wie
1
#define SQUARE(x) ((x) * (x))
Also ein Makro mit Parametern. So etwas kann leider böse daneben gehen, 
wenn man z.B. SQUARE(i++) aufruft. Denn es basiert nach wie vor auf 
Textersetzung und expandiert daher zu
1
((i++) + (i++))

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Detlev T. schrieb:
> So etwas wie

"function-like macro" im englischen (C-)Standard.

Ich glaube "funktionsartiger Makro" wäre eine bessere deutsche
Übersetzung als "Makro-Funktion".  Letzteres würde gemäß den Regeln
der deutschen Sprache eine Funktion implizieren, aber es ist keine
sondern ein Makro.

von Coder (Gast)


Lesenswert?

@Jörg

Ja, das habe ich schlecht "eingedeuscht"; die "function-like macro" 
meinte ich. Funktionartiger Makro klingt gut und besser ist übersetzt 
:-).

von Ferdinand (Gast)


Lesenswert?

Detlev T. schrieb:
> So etwas kann leider böse daneben gehen,
> wenn man z.B. SQUARE(i++) aufruft. Denn es basiert nach wie vor auf
> Textersetzung und expandiert daher zu((i++) + (i++))

Das verstehe ich nicht.
Es müsste doch zu
(i++) * (i++))
expandieren und damit wieder passen?

von (prx) A. K. (prx)


Lesenswert?

Und was kommt bei i=2 dabei raus?

von Detlev T. (detlevt)


Lesenswert?

Ferdinand schrieb:
> Es müsste doch zu
> (i++) * (i++))
> expandieren und damit wieder passen?

Natürlich "*" statt "+". Da war wohl die Shift-Taste zu langsam...

Das ist aber nicht der Punkt. Die Variable i wird zweimal erhöht anstatt 
einmal, wie SQUARE(i++) suggeriert. Auch dürfte das Ergebnis nicht i*i 
sondern i*(i+1) sein, da die Variable nach Auswertung des ersten 
Ausdruckes bereits erhöht wurde.

AUf jeden Fall dürfte da kaum das heraus kommen, was der Programmierer 
beabsichtigte.

von (prx) A. K. (prx)


Lesenswert?

Detlev T. schrieb:
> Auch dürfte das Ergebnis nicht i*i
> sondern i*(i+1) sein, da die Variable nach Auswertung des ersten
> Ausdruckes bereits erhöht wurde.

Kann sein, muss nicht sein.

von Ferdinand (Gast)


Lesenswert?

Detlev T. schrieb:
> Die Variable i wird zweimal erhöht anstatt
> einmal, wie SQUARE(i++) suggeriert.

Stimmt, jetzt sehe ich es auch.

von Andreas B. (andreasb)


Lesenswert?

Ferdinand schrieb:
> Detlev T. schrieb:
>> Die Variable i wird zweimal erhöht anstatt
>> einmal, wie SQUARE(i++) suggeriert.
>
> Stimmt, jetzt sehe ich es auch.

Ich habs gerate ausprobiert...
1
#define SQUARE(x) ((x) * (x))
2
3
int i = 10;
4
5
printf("a = %i\n", i);
6
printf("b = %i\n", SQUARE(i++));
7
printf("c = %i\n", i);

./test
a = 10
b = 100
c = 12

;-)

Naja, da hat der Optimizer zugeschlagen?

Ich weiss schon warum ich so Precompiler Makros nicht mag...


mfg Andreas

von (prx) A. K. (prx)


Lesenswert?

Andreas B. schrieb:
> Naja, da hat der Optimizer zugeschlagen?

Das ist eines von 2 zulässigen Resultaten, wobei das auch vom Grad der 
Optimierung und wohl auch der Luftfeuchtigkeit abhängen darf. Es ist 
schlicht offen, wann die Inkrementierungen durchgeführt werden, Pflicht 
ist das erst beim nächsten sequence point.

von Johannes V. (johannes_v)


Lesenswert?

Ich habe vor kurzem angefangen auf meinen ARM's Vererbung und 
Virtualisierung geziehlt einzusetzen. IMHO überwiegen die Vorteile durch 
Lesbarkeit und Produktivitätssteigerung enorm. Ohne groß mit den Flags 
zu spielen macht er bei -o2/3 das alles schon recht gut
1
  mov r4,  r0
2
  ldr r3, [r4]
3
  blx r3

Also idr 2 Befehele mehr, ein paar Tabellen im ROM, welcher eh riesieg 
ist und ein Vptr an der Front der Objekte. Im Hobbybereich kosten die 
Chips auch alle gleichviel und sollte es mal nicht mehr reichen, nehm 
ich halt den nächst Größeren. Das Rummgefummel und der Kampf ums letzte 
bit ist mir die Zeit echt nicht mehr Wert.

von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Andreas B. schrieb:
>> Naja, da hat der Optimizer zugeschlagen?
>
> Das ist eines von 2 zulässigen Resultaten, wobei das auch vom Grad der
> Optimierung und wohl auch der Luftfeuchtigkeit abhängen darf. Es ist
> schlicht offen, wann die Inkrementierungen durchgeführt werden,

Es ist sogar offen, ob sie überhaupt durchgeführt werden und ob danach 
noch irgendwas sinnvolles ausgeführt wird. Nicht nur die Reihenfolge ist 
nicht vorgegeben, sondern das gesamte Verhalten ist laut ISO C (und auch 
C++) undefiniert.

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.