C++ auf AVR: new und virtuelle Methoden

#3013497
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
#3013536
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.
#3013556
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.
#3013583
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.
#3013649
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.
#3013695
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
Gast #3013825
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.
Gast #3013835
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 ;-)
#3013903
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.
Persönliche Seite #3014915
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...
#3015015
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 :-)
#3015038
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.
#3015418
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 :-)
#3015485
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 :-)
#3028186
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.
#3028222
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
#3028619
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.
#3028643
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren