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
Detlev T. schrieb:> Schon 'mal gemacht? Geht's?
Was hindert dich daran, es auszuprobieren?
1
#include<avr/io.h>
2
3
classA
4
{
5
public:
6
virtualvoiddoit(){PORTB|=(1<<PB0);}
7
};
8
9
classB:publicA
10
{
11
public:
12
virtualvoiddoit(){PORTB|=(1<<PB1);}
13
};
14
15
voidfoo(A*obj)
16
{
17
obj->doit();
18
}
19
20
intmain()
21
{
22
DDRB=0x03;
23
PORTB=0x00;
24
25
BobjB;
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.
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.
@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.
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
BobjB;
2
3
foo(&objB);
ein
1
B*objB=newB;
2
3
foo(objB);
zu machen.
Richtig. Nichts.
Ausser das DU es tun müsstest.
@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
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.
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 ;-)
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.
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:
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...
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.
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.
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 :-)
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.
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?
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 :-)
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:
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 :-)
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.
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.
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"
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".
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
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.
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?
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.
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.
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
inti=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
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.
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
movr4,r0
2
ldrr3,[r4]
3
blxr3
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.
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.