Datum:
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
Datum:
Detlev T. schrieb: > Schon 'mal gemacht? Geht's? Was hindert dich daran, es auszuprobieren?
#include <avr/io.h> class A { public: virtual void doit() { PORTB |= ( 1 << PB0); } }; class B : public A { public: virtual void doit() { PORTB |= ( 1 << PB1 ); } }; void foo( A* obj ) { obj->doit(); } int main() { DDRB = 0x03; PORTB = 0x00; B objB; foo( &objB ); while( 1 ) ; } |
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.
Datum:
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.
Datum:
@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.
Datum:
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
B objB; foo( &objB ); |
ein
B* objB = new B; foo( objB ); |
zu machen. Richtig. Nichts. Ausser das DU es tun müsstest.
Datum:
@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
Datum:
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.
Datum:
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.
Datum:
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:
main: ldi r24,lo8(3) out 0x17,r24 out 0x18,__zero_reg__ sbi 0x18,1 .L4: rjmp .L4 |
Es funktioniert also.
Datum:
Was auch zeigt, wie "riesig" der C++ Overhead sein kann ;-)
Datum:
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...
Datum:
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.
Datum:
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.
Datum:
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: > >
> main: > ldi r24,lo8(3) > out 0x17,r24 > out 0x18,__zero_reg__ > sbi 0x18,1 > .L4: > rjmp .L4 > |
> > Es funktioniert also. :-) Das ist fies. Ist ja alles geinlined worden :-)
Datum:
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.
Datum:
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?
Datum:
Roland H. schrieb: > Oha. Johann, welche Optimierungen waren denn das bitte? -Os -fwhole-program genügt bei mir.
Datum:
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 :-)
Datum:
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:
000000a6 <A::doit()>: a6: 28 9a sbi 0x05, 0 ; 5 a8: 08 95 ret 000000aa <B::doit()>: aa: 29 9a sbi 0x05, 1 ; 5 ac: 08 95 ret 000000ae <main>: ae: cf 93 push r28 b0: df 93 push r29 b2: 00 d0 rcall .+0 ; 0xb4 <main+0x6> b4: cd b7 in r28, 0x3d ; 61 b6: de b7 in r29, 0x3e ; 62 b8: 83 e0 ldi r24, 0x03 ; 3 ba: 84 b9 out 0x04, r24 ; 4 bc: 15 b8 out 0x05, r1 ; 5 be: 84 e0 ldi r24, 0x04 ; 4 c0: 91 e0 ldi r25, 0x01 ; 1 c2: 9a 83 std Y+2, r25 ; 0x02 c4: 89 83 std Y+1, r24 ; 0x01 c6: ce 01 movw r24, r28 c8: 01 96 adiw r24, 0x01 ; 1 ca: 0e 94 55 00 call 0xaa ; 0xaa <B::doit()> ce: ff cf rjmp .-2 ; 0xce <main+0x20> |
Kompiliert mit
/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 /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 /home/gcc-avr-4.6.2/data/gcc-toolchains-precompiled/avr/avr8-gnu-toolchain-3.4.0.663/bin/avr-gcc --version 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 :-)
Datum:
Roland H. schrieb: >> -Os -fwhole-program genügt bei mir. > > Bei mir immer noch kein Erfolg. Welcher GCC kam zum Einsatz? 4.7.2
Datum:
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.
Datum:
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.
Datum:
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.
Datum:
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"
Datum:
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".
Datum:
Wie gesagt, ich hatte gerade eine 4.8 zur Hand; mit der 4.7 geht das bestimmt genauso.
Datum:
Juhu... die Makro-Funktionen sterben aus und der C Code kann MISRAtener werden.
Datum:
Was ist denn eine Makro-Funktion? Sowas wie eine Header-Bibliothek?
Datum:
Johann L. schrieb: > Was ist denn eine Makro-Funktion? Sowas wie eine Header-Bibliothek? So etwas wie
#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
((i++) + (i++)) |
Datum:
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.
Datum:
@Jörg Ja, das habe ich schlecht "eingedeuscht"; die "function-like macro" meinte ich. Funktionartiger Makro klingt gut und besser ist übersetzt :-).
Datum:
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?
Beitrag #3028176 wurde vom Autor gelöscht.
Datum:
Und was kommt bei i=2 dabei raus?
Datum:
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.
Datum:
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.
Datum:
Detlev T. schrieb: > Die Variable i wird zweimal erhöht anstatt > einmal, wie SQUARE(i++) suggeriert. Stimmt, jetzt sehe ich es auch.
Datum:
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...
#define SQUARE(x) ((x) * (x)) int i = 10; printf("a = %i\n", i); printf("b = %i\n", SQUARE(i++)); 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
Datum:
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.
Datum:
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
mov r4, r0 ldr r3, [r4] 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.
Datum:
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.