mikrocontroller.net

Forum: Compiler & IDEs Objektorientierung mit avr gcc


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute,

Programmiert jemand von euch in c++ (objektorientiert) auf den Atmel 
8-Bit Controllern ?
Wenn ja, wie einfach nur zum Beispiel eine Klasse für twi eine für den 
usart etc mit entsprechenden Feldern, Methoden Konstruktor und 
Destruktor ?
Wenn jemand mit dem Objektorientierten Ansatz arbeitet wäre es nett, 
wenn kurz erläutert werden würde, auf was man dabei achten muss.

Grüße

Sven

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:

> Programmiert jemand von euch in c++ (objektorientiert) auf den Atmel
> 8-Bit Controllern ?

Ja.

Allerdings ist das ein deutlich eingeschränktes C++. Dynamische 
Speicherverwaltung, STL und Konsorten sind eher unangebracht.

Funktional nützlich ist das beispielsweise, wenn man Sensoren 
verschiedener Typen einsetzt, wie DS1820 und LM335, die sich über 
Ableitung von Klassen elegant mit einem einheitlichen Interface versehen 
lassen.

Templates sind in bestimmten Fällen auch recht nützlich, beispielsweise 
zur Spezifizierung von Parametern von Treiber für I/O-Module, wie etwa 
Puffergrössen.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Thema kam hier schon mal auf; vielleicht hilft die Suchfunktion.
Z.B. hier: Beitrag "Festkommazahlen in Würde"

Der Grundtenor ist:
- ja, es geht
- C++ muß nicht langsamer sein oder mehr Code produzieren, kann es aber
- man muß sich zurückhalten
- die STL kann man sich komplett abschminken
- Mit templates kann man mit wenigen Zeilen viel Code erzeugen
  lassen; man sollte wissen was man tut.
- Mit Ableitungen und virtuellen Methoden ist ebenfalls nicht zu
  spaßen. Im Einzelfall sinnvoll, aber man muß auf generierte
  Codegröße und Speicherverbrauch achten.
- Überladen von Operatoren kann sehr hilfreich sein und den
  Quelltext enorm verbessern. Aber auch hier muß man sich
  bewusst machen, was dahinter steht hinsichtlich Code und
  Laufzeit.

Zum Schreiben auf UART habe ich auch mal eine Klasse gebaut, weil
ich es leid war, jedesmal wieder zu überlegen, was auf dem heutigen
AVR anders ist als beim gestrigen hinsichtlich Registernamen etc..

Ebenso in C++ ist eine Text-LCD-Klasse von mir, die ich von der
Benutzung her wesentlich angenehmer finde als das hier übliche
Vorgehen mit einer Headerdatei, in der Pins eingestellt werden
müssen, bevor man sie verwenden kann - auch wenn letztlich
intern nichts anderes passiert natürlich.

Ein #include "..." und Methoden aufrufen finde ich angemessener als
in jedem Quelltext wieder mit low leve-Kram zu fummeln.
Das Gefummel gehört einmal in eine Headerdatei, und gut.

Autor: Stefan H. (stefan_h16)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:
> Hallo Leute,
>
> Programmiert jemand von euch in c++ (objektorientiert) auf den Atmel
> 8-Bit Controllern ?
> Wenn ja, wie einfach nur zum Beispiel eine Klasse für twi eine für den
> usart etc mit entsprechenden Feldern, Methoden Konstruktor und
> Destruktor ?
> Wenn jemand mit dem Objektorientierten Ansatz arbeitet wäre es nett,
> wenn kurz erläutert werden würde, auf was man dabei achten muss.
>
> Grüße
>
> Sven

Man kann ganz gut C++ Programmieren auf AVRs. Der Erfolg der Arduino 
-Plattform beweist das es geht. Auch wenn Puristen laut aufschreien.

Wie Klaus schon gesagt hat: Man sollte beim Vererbungen und virtuellen 
Funktionen sehr vorsichtig sein, den diese erzeugen einen riesigen 
Overhead wie alles was an Objektorientierung nicht zur Compilezeit 
aufgelöste werden kann. Templates und Operatorüberladung können einen 
Quelltext übersichtlicher machen, man muss halt aufpassen und wissen was 
der Compiler draus macht.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan H. schrieb:

> Wie Klaus schon gesagt hat: Man sollte beim Vererbungen und virtuellen
> Funktionen sehr vorsichtig sein, den diese erzeugen einen riesigen
> Overhead

Riesig? Overhead ja, aber riesig ist der nicht. Braucht halt auf den 
AVRs ein bisschen mehr RAM, weil die als Harvard-Kisten die VFTs nicht 
ins ROM kriegen. Das kann insbesondere auf den Tinys spürbar werden. Von 
Code her ist es nicht so arg.

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hm, gibt es irgendwo mal Beispiele für die Verwendung von C++ auf dem 
AVR? Also mal ein einfaches, aber vollständiges Projekt mit Makefile 
usw.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Siehe meinen Link oben

gerne nochmal:
Beitrag "Festkommazahlen in Würde"


oder hier noch meine LCD-Version:
Beitrag "Re: [AVR|C] LCD Routinen"

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke. Aber sowas wie die LCD Routine würde ich glaube ich nie 
verwenden. Man braucht ja schon einen ATmega16 nur wegen der Routine ;)

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
?

Wie sieht der Vergleich mit einer gleichwertigen C-Version aus?

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das hat jetzt nichts mit C oder C++ zu tun. Ich wüsste nur nicht, wieso 
man so viel Code für unterschiedliche Displays usw da rein packen muss. 
Zur Laufzeit hat man selten die Notwendigkeit, ein anderes Display zu 
verwenden.

Meine USART Routine hat nichtmal eine Funktion, die Baudrate zu ändern. 
Das geschieht alles zur Compilezeit.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In meiner C++-LCD-Variante wird es in aller Regel so sein, daß man eben 
auch nur eine LCD-Variante hat, der Compiler konstante Werte als solche 
erkennt und vom ganzen Code eh alles wegoptimiert, was nicht genutzt 
wird.
Die Methoden sind alle inline, und die ganzen Fallunterscheidungen 
hängen nur von Konstanten ab.

Deshalb sieht es im Quelltext beeindruckend aus, aber nach dem 
Kompilieren sollte nicht mehr übrig sein als bei einer hingefummelten 
C-Version für einen konkreten Fall.
Was in meiner C++-Version höchstens mehr Code erzeugt als in der hier 
üblichen C-Version ist die beliebige Verteilung der Anschlüsse auf Ports 
und Pins. Ene handgeschneiderte Version, bei der alle Pins in 
codefreundlicher Reihenfolge (D4-D7 auf Pin 0-3) ist sicher etwas 
kleiner.

Aber bei ähnlichem Funktionsumfang ist C++ hier im Endeffekt nicht oder 
nur unwesentlich fetter.
Gerade deshalb finde ich es ein nettes Beispiel für dieses Thema.

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das sollte jetzt keine Kritik an deiner C++ Implementierung sein. Ich 
hab es mal compiliert wie es war und bekam fast 13kByte Code raus, wenn 
ich mich nicht irre.

Die C Lib, die ich mal geschrieben habe, kann vielleicht nicht ganz so 
viel (z.B. printf), aber dafür würde so ein Programm nur 2-3kByte 
brauchen.

Ich meine, was soll der Compiler da groß wegoptimieren? switch(LCD_Type) 
dürfte wohl komplett im Flash landen. Oder ist der Compiler so 
intelligent, dass er erkennt, welche Cases nicht benutzt werden?

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:
> Das sollte jetzt keine Kritik an deiner C++ Implementierung sein. Ich
> hab es mal compiliert wie es war und bekam fast 13kByte Code raus, wenn
> ich mich nicht irre.

Das ist heftig.
Meine LCD-Lib benötigt 290 Byte:

http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=102296

LCDs mit 2 Enable sind nicht drin, das läßt sich aber leicht nachrüsten.
Etwas lästig ist, daß die Pindefinitionen immer zweimal gemacht werden 
müssen, je einmal für Ausgangs- und Richtungsbit.


Peter

Autor: Alexander B. (tecnologic) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

Kennt jemand ein gutes Buch für Embedded C++, vllt für Cortex M3 oder 
so?

MfG

Tec

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:

> Das sollte jetzt keine Kritik an deiner C++ Implementierung sein. Ich
> hab es mal compiliert wie es war und bekam fast 13kByte Code raus, wenn
> ich mich nicht irre.

Das hängt wohl davon ab, wie man damit umgeht. Wenn man daraus eine 
globale Variable macht, dann hat man verloren, denn dann wird der ganze 
Code minutiös eingebaut. Er hatte das wohl so vorgesehen, dass man in 
jeder Routine diese Variable lokal definiert, wie in seinem 
Beispielcode. Dann fieselt der Compiler durch Datenflussanalyse die 
ganzen Konstanten raus und optimiert es.

Ich würde das per Template realisieren, mit den Invarianten als 
Parameter des Templates. An passende Stelle kommt dann ein typedef mit 
den entsprechenden Werten als Argumente und damit kann man dann 
arbeiten. Sämtliche Pseudo-Variablen, die hier doch nur Konstanten 
durchreichen, entfallen dann.

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. schrieb:
> Das hängt wohl davon ab, wie man damit umgeht.

Sicher. Aber ich bin generell der Anhänger der "Compile Time 
Definitionen". Also schon vor dem compilieren festlegen, um welches 
Display es sich handelt oder welche Baudrate ein Comport haben soll.

Der Compiler kann noch so gut sein, aber den zusätzlichen Code kann er 
auch nicht wegoptimieren.

Nur mal ein Beispiel. Den Code macht nicht viel mehr als die Ausgabe von 
zwei Texten und erzeugt folgendes:
Size after:
AVR_LCD44780.elf  :
section     size      addr
.text      12976         0
.data         20   8388704
.stab      10944         0
.stabstr    3405         0
Total      27345

Mein Beispiel hat noch USART mit drin und ließt Text aus dem Flash und 
erzeugt das
Size after:
main.elf  :
section            size      addr
.text               972         0
.bss                 71   8388704
.stab              1716         0
.stabstr             84         0
Total             11480

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:

> Der Compiler kann noch so gut sein, aber den zusätzlichen Code kann er
> auch nicht wegoptimieren.

Doch, im Prinzip schon.

Mit einer Ausnahme können sämliche switch(LCD_Type)/case Zweige als 
toter Code komplett rausfliegen, wenn der Compiler LCD_Type als 
Konstante ansieht.

Indirekte Portzugriffe werden zu direkten Zugriffen, wenn der Pointer 
konstant ist. Dynamische Shifts werden zu konstanten Masken, usw...

Nicht verwendete Funktionen erzeugen keinerlei Code. Allerdings sehe ich 
bei ihm das Risiko, dass durch das Inlining der gleiche Code vielfach 
repliziert wird.

Was dann noch bleibt ist der generische Umgang mit den einzelnen 
Portpins. Andere Libs gehen gern mal davon aus, dass die 4 Datenbits 
nebeneinander auf dem gleichen Port stehen.

Der Trick besteht darin, das so hinzudeichseln, dass die ganzen 
Instanzvariablen, die durch die Konstruktorparameter initialisiert 
werden, vom Compiler auch als Konstanten angesehen werden. Da könnte 
auch helfen, die Variable "lcd" als Konstante zu definieren.

Mit Templates sieht der Quellcode ziemlich ähnlich aus, man hängt aber 
nicht so an der Optimierung, weil der Umweg über die Instanzvariablen 
entfällt. Der Zwang zum Inlining entfällt hier, so dass auch die 
Replikation von Code nicht auftritt.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:
> Ich
> hab es mal compiliert wie es war und bekam fast 13kByte Code raus, wenn
> ich mich nicht irre.
>
> Die C Lib, die ich mal geschrieben habe, kann vielleicht nicht ganz so
> viel (z.B. printf), aber dafür würde so ein Programm nur 2-3kByte
> brauchen.

Deshalb hatte ich oben schon mal gefragt, was du eigentlich vergleichst.
Wenn du die C++-Version mit printf nimmst, aber das nicht in der eigenen 
Version nutzt, kannst man das nicht vergleichen.
Mit printf bist bei deiner Version sicher auch gleich über 2 kB rübe, 
wenn du es in der C++-Version nicht nimmst, wird es kleiner, da ja wie 
gesagt alles inline ist.

Es ist sinnlos irgendwas mit irgendwas zu vergleichen, und daraus 
Schlüsse zu ziehen.
Nur Zahlen in den Raum zu werfen ist Zeitverschwendung.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. schrieb:
> Mit Templates sieht der Quellcode ziemlich ähnlich aus, man hängt aber
> nicht so an der Optimierung, weil der Umweg über die Instanzvariablen
> entfällt. Der Zwang zum Inlining entfällt hier, so dass auch die
> Replikation von Code nicht auftritt.

Dafür hast bei z.B. bei zwei LCDs, die sich ja im Enable-Parameter 
unterscheiden, dann gleich zwei komplette Klassen, die nichts mehr 
gemeinsam nutzen.

Die eierlegende Wollmilchsau gibt es halt nicht.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:

> Dafür hast bei z.B. bei zwei LCDs, die sich ja im Enable-Parameter
> unterscheiden, dann gleich zwei komplette Klassen, die nichts mehr
> gemeinsam nutzen.

Ja, man kann nicht alles haben.

In anderen Fällen ist dieser Aspekt verbreiteter, wie beispielsweise 
Treibers für UARTs, denn davon gibts oft mehrere. Ich löse das dann über 
eine normale Basisklasse, die den grössten Teil des Code enthält, und 
einem davon abgeleiteten Template in dem beispielsweise die Puffer und 
Interrupt-Wrapper liegen.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zurück zur Ursprungsfrage:
Das mit C++ ist alles gar nicht so schwer :-)

Autor: Peter D. (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Dafür hast bei z.B. bei zwei LCDs, die sich ja im Enable-Parameter
> unterscheiden, dann gleich zwei komplette Klassen, die nichts mehr
> gemeinsam nutzen.

Man kann sie aber auch als ein großes LCD betrachten.
Dann muß man nur die Funktionen lcd_nibble() und lcd_pos() etwas pimpen:
static uint8_t lcd_e01;                      // controller select


static void lcd_nibble( uint8_t d )
{
  LCD_D4 = 0; if( d & 1<<4 ) LCD_D4 = 1;
  LCD_D5 = 0; if( d & 1<<5 ) LCD_D5 = 1;
  LCD_D6 = 0; if( d & 1<<6 ) LCD_D6 = 1;
  LCD_D7 = 0; if( d & 1<<7 ) LCD_D7 = 1;

  if( lcd_e01 & 1<<0 ) LCD_E0 = 1;
  if( lcd_e01 & 1<<1 ) LCD_E1 = 1;
  _delay_us( 1 );                       // 1us
  LCD_E0 = 0;
  LCD_E1 = 0;
}

void lcd_pos( uint8_t line, uint8_t column )
{
  lcd_e01 = 1;                          // 1./2. line
  if( line & 2 )
    lcd_e01 = 2;                        // 3./4. line

  if( line & 1 )
    column += 64;

  lcd_command( 0x80 + column );
}

Kostet vielleicht 20 Byte mehr.


Peter

Autor: sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie läuft das dann mit dem compilieren ?
kann ich den avr gcc auch für c++ nutzen oder gibt es einen avr g++ ?
Ich hab mir das mit der oop einfach so gedacht, das ich für alles usart, 
twi ,timer etc eine eigene basisklasse schreibe um mir alles 
übersichtlicher und einfacher zu machen.
kann avr studio c++ code übersetzen ?

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
davon gehe ich aus; der avr-gcc unter Linux ist gleichzeitig C++ 
(avr-g++).

Autor: sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe beim googeln aufgeschnappt das man sich den new bzw. delete 
operator selbst schreiben muss wenn man avr g++ nutzen möchte ?
Ist da was drann ? eigentlich müssten new und delete doch schon 
implementiert sein, da es sich um g++ handelt ?

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dynamische Speicherverwaltung auf Systemen mit nur ein paar KB RAM ist
nicht sehr sinnvoll. Wenn man sie braucht, schreibt man sich eine auf
bestimmte Datentypen zugeschnittene Version selber.

In der AVR-Libc gibt es malloc und free, die du prinzipiell für die
new- und delete-Operatoren verwenden kannst. Aber du wirst glücklicher,
wenn du auf new und delete verzichtest.

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich kann also auch über die c-funktion malloc ein Objekt (Instanz) 
erzeugen ? Weshalb gibt es dann in c++ new bzw. delete ?

Was haltet ihr davon in z.b. einer usart klasse für den mega16/32 im 
private bereich für jedes Register das den usart betrifft ein bitfeld 
anzulegen, um dann über einsprechende methoden auf das entsprechende bit 
im register zugreifen zu könnenn ?

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:
> Weshalb gibt es dann in c++ new bzw. delete ?

Weil die gleich ctor bzw. dtor aufrufen, malloc und free nicht.

Außerdem gelingt new immer in dem Sinne, daß es entweder einen gültigen 
Zeiger liefert, oder notfalls eine Ausnahme wirft.
Eben letzteres ist auch nicht unbedingt erstrebenswert auf einem AVR 
(bzw. gar nicht möglich).

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:
> Der Compiler kann noch so gut sein, aber den zusätzlichen Code kann er
> auch nicht wegoptimieren.
>
> Nur mal ein Beispiel. Den Code macht nicht viel mehr als die Ausgabe von
> zwei Texten und erzeugt folgendes:Size after:
> AVR_LCD44780.elf  :
> section     size      addr
> .text      12976         0
> .data         20   8388704
> .stab      10944         0
> .stabstr    3405         0
> Total      27345

Ich habe mal ein minimales Beispiel gemacht:
#include "LCD44780.h"

int main( int nargs, char **args )
{
  AnyWare::LCD44780    lcd( AnyWare::LCD44780::LCD_Type_4x20,
                            PORTB, 0, // Port, Pin LCD-DB4
                            PORTB, 1, // Port, Pin LCD-DB5
                            PORTB, 2, // Port, Pin LCD-DB6
                            PORTB, 3, // Port, Pin LCD-DB7
                            PORTB, 4, // Port, Pin LCD-RS
                            PORTB, 5  // Port, Pin LCD-Enable1
                            );

  lcd.printSZ( "Hallo" );

  while( "warten aufs juengste Gericht" )
  {
  }

  return 0;
}

Das gibt bei mir mit -Os und kleinster C-Lib:
AVR_LCD44780.elf  :
section    size      addr
.text      3034         0
.data         6   8388704
.stab      1716         0
.stabstr     84         0
Total      4840

Also noch einige Luft auf einem atmega8...

(übrigens mit dem viel gescholtenen gcc 4.3.2)

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:
> Was haltet ihr davon in z.b. einer usart klasse für den mega16/32 im
> private bereich für jedes Register das den usart betrifft ein bitfeld
> anzulegen, um dann über einsprechende methoden auf das entsprechende bit
> im register zugreifen zu könnenn ?

Das mache ich wesentlich lieber, als die übliche Bitschieberei, alleine 
wegen der Lesbarkeit und m.E. geringeren Fehleranfälligkeit.

Scheint hier aber nicht sehr verbreitet zu sein...

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Ich habe mal ein minimales Beispiel gemacht:

Wie komme ich dann auf über 12k?

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
das darfst du mich nicht fragen :-)

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> das darfst du mich nicht fragen :-)

Wieso, ich hab dein Makefile verwendet ;)

Ich hab nur das Archiv ausgepackt und make eingegeben.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In die ZIP-Datei von meinem Link habe ich nicht reingeschaut, sondern 
eine Version bei mir auf der Platte genommen.
Da stand vom letzten Herumprobieren noch -O3 und die volle C-Lib mit 
printf und float drin; das habe ich zu -Os und minimale C-Lib geändert.
Das kann gut der Unterschied sein.

Dann kommt die Größe aber ja nicht aus meiner LCD-Geschichte, sondern 
unsinnigerweise aus der Lib.

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok, so genau hab ich nicht rein geschaut. Aber 3k ist immer noch 
verhältnismäßig viel

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Ich habe mal ein minimales Beispiel gemacht:
> ...
> Das gibt bei mir mit -Os und kleinster C-Lib:
> AVR_LCD44780.elf  :
> section    size      addr
> .text      3034         0
> .data         6   8388704
> .stab      1716         0
> .stabstr     84         0
> Total      4840
>
> Also noch einige Luft auf einem atmega8...
>
> (übrigens mit dem viel gescholtenen gcc 4.3.2)

Gleiches Beispiel mit dem weniger gescholtenen (da noch nicht so weit
verbreiteten) GCC 4.6.1 (-Os, -mmcu=atmega8) und der Default-C-Lib
(*printf wird nicht gelinkt, da nicht benutzt):
AVR_LCD44780.elf  :
section    size      addr
.text       992         0
.data         6   8388704
.stab      2100         0
.stabstr    156         0
Total      3254

Dass immer noch ein knappes KB übrig bleibt, ist darauf zurückzuführen,
dass die Anschlusspins des LCDs in der Klasse erst zur Laufzeit
festgelegt werden, was aufwendige Bitschiebereien (insbesondere in
writeByte) bedingt. Würde man die Pins als Konstanten definieren,
könnte man noch ein paar hundert Bytes einsparen, so dass die Codegröße
vermutlich in die Größenordnung des Codes von Peter rücken wird.

Das Beispiel (Sprung von 3034 auf 992 Bytes) zeigt übrigens, dass neuere
GCC-Versionen nicht immer schlechter optimieren als die alten :)

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das war mit meiner C++-Version der LCD-Lib?
Dann muß ich auch den gcc updaten!

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. schrieb:
> Stefan H. schrieb:
>
>> Wie Klaus schon gesagt hat: Man sollte beim Vererbungen und virtuellen
>> Funktionen sehr vorsichtig sein, den diese erzeugen einen riesigen
>> Overhead
>
> Riesig? Overhead ja, aber riesig ist der nicht. Braucht halt auf den
> AVRs ein bisschen mehr RAM, weil die als Harvard-Kisten die VFTs nicht
> ins ROM kriegen.

Der AVR hat damit kein Problem. Es ist der gcc, der das nicht 
hinbekommt, weil er keine Unterstützung für mehere Speichertypen 
besitzt.

Klaus Wachtler schrieb:
> davon gehe ich aus; der avr-gcc unter Linux ist gleichzeitig C++
> (avr-g++).

Nicht ganz. der avr-g++ ist der reine C++-Compiler. Der avr-gcc ist das 
generische Front-End, das beim compilieren anhand des Dateinamens den 
Typ erkennt. Aber eben nur beim compilieren, nicht beim linken. Da 
braucht man avr-g++ (oder "avr-gcc -X c++").

sven schrieb:
> Ich habe beim googeln aufgeschnappt das man sich den new bzw. delete
> operator selbst schreiben muss wenn man avr g++ nutzen möchte ?
> Ist da was drann ?

Ja.

> eigentlich müssten new und delete doch schon implementiert sein, da es
> sich um g++ handelt ?

Die Implementation steht in der zum gcc gehörenden libsupc++, die aber 
für den avr nicht existiert. Es hat einfach noch keiner die Anpassungen 
gemacht, die für eine funktionierende Version nötig wären.

Sven schrieb:
> Ich kann also auch über die c-funktion malloc ein Objekt (Instanz)
> erzeugen ?

Nein. Du kannst Speicher dafür reservieren. Zum Erzeugen einer Instanz 
gehören aber noch Konstruktoren und andere Dinge.

Yalu X. schrieb:
> Dynamische Speicherverwaltung auf Systemen mit nur ein paar KB RAM ist
> nicht sehr sinnvoll. Wenn man sie braucht, schreibt man sich eine auf
> bestimmte Datentypen zugeschnittene Version selber.

Naja, wenn man Polymporphie einsetzen will, gehört dazu normalerweise 
auch die dynamische Erzeugung mit new. Hier kann man auf dem AVR aber 
z.B. mit placement new arbeiten. Dem übergibt man die Adresse von schon 
reserviertem Speicher. So kann man das Objekt dynamisch erzeugen, aber 
in statischem Speicher halten. Man muß nur dafür sorgen, daß der 
Speicher groß genug ist für das  Objekt.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Klaus Wachtler schrieb:
>> davon gehe ich aus; der avr-gcc unter Linux ist gleichzeitig C++
>> (avr-g++).
>
> Nicht ganz. der avr-g++ ist der reine C++-Compiler. Der avr-gcc ist das
> generische Front-End, das beim compilieren anhand des Dateinamens den
> Typ erkennt. Aber eben nur beim compilieren, nicht beim linken. Da
> braucht man avr-g++ (oder "avr-gcc -X c++").

Ich habe mein Beispiel mit avr-gcc erzeugt.

Du hast natürlich recht: das ist nur das Frontend, es erkennt an der 
Dateiendung, ob es sich um C oder C++ handelt und kompiliert 
entsprechend.
Was das Kompilieren angeht, ist es also erstmal egal, ob ich avr-gcc 
oder avr-g++ aufrufe.

Weiterhin hast du auch recht, was das Linken angeht:
Bei avr-gcc werden standardmäßig nur die C-Libs dazugepackt, bei avr-g++ 
eben auch die für C++.
Das ist aber beim AVR nicht relevant, weil eh nur gegen die avrlibc 
gelinkt wird.

Auf einem PC bspw. kann man ebenfalls eine C++-Datei mit gcc (statt g++) 
kompilieren, muß dann aber beim Linken mit -l... die C++-Lib dazu 
bestellen.
Das ist hier aber irrelevant; deshalb sind in meinem Beispiel avr-gcc 
und avr-g++ letztlich gleichwertig.

Autor: Markus B. (markus_b77)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Um mal wieder zum Topic zu kommen

http://xpcc.sourceforge.net/

Autor: Yalu X. (yalu) (Moderator)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Das war mit meiner C++-Version der LCD-Lib?
> Dann muß ich auch den gcc updaten!

Yep. Damit du siehst, welche Compiler- und Linker-Optionen ich geändert
habe, habe ich das geänderte Makefile angehängt.

GCC 4.3.6 mit dem gleichen Makefile liefert bei mir folgendes Ergebnis:
AVR_LCD44780.elf  :
section    size      addr
.text      2718         0
.data         6   8388704
.stab      1740         0
.stabstr     84         0
Total      4548

Die AVR-Libc ist jeweils Version 1.7.1 und mit GCC 4.2.4 gebaut.

Der Unterschied zwischen GCC 4.3.6 und 4.6.1 liegt vor allem darin
begründet, dass 4.3.6 den Konstruktor der LCD-Klasse nicht inlinet,
weswegen das rieseige Case-Konstrukt in voller Länge stehen bleibt.

Stattdessen inlinet 4.3.6 ungünstigerweise die Methode enable, die
sehr oft aufgerufen wird.

Wenn man LCD44780 und enable mit den Attributen always_inline bzw.
noinline versieht, sieht auch beim 4.3.6 das Ergebnis deutlich besser
aus:
AVR_LCD44780.elf  :
section    size      addr
.text      1126         0
.data         6   8388704
.stab      1740         0
.stabstr     84         0
Total      2956

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> A. K. schrieb:
>> Riesig? Overhead ja, aber riesig ist der nicht. Braucht halt auf den
>> AVRs ein bisschen mehr RAM, weil die als Harvard-Kisten die VFTs nicht
>> ins ROM kriegen.
>
> Der AVR hat damit kein Problem. Es ist der gcc, der das nicht
> hinbekommt, weil er keine Unterstützung für mehere Speichertypen
> besitzt.

GCC könnte es schon, nur ist das im avr-Teil (noch) nicht eingebaut.
Allerdings wäre das erst die halbe Miete für "vtables im Flash",
aber immerhin würde es das ganze pgm_read-Geraffel überflüssig machen.

> Da braucht man avr-g++ (oder "avr-gcc -X c++").

avr-gcc -x c++

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johann L. schrieb:
>> Der AVR hat damit kein Problem. Es ist der gcc, der das nicht
>> hinbekommt, weil er keine Unterstützung für mehere Speichertypen
>> besitzt.
>
> GCC könnte es schon, nur ist das im avr-Teil (noch) nicht eingebaut.

Ist das neu? Als ich das letzte Mal (genau wegen dieser Thematik) in den 
gcc-Eingeweiden rumgewühlt hab, hab ich da nichts gefunden.

>> Da braucht man avr-g++ (oder "avr-gcc -X c++").
>
> avr-gcc -x c++

Richtig. War ein Tippfehler.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Johann L. schrieb:
>>> Der AVR hat damit kein Problem. Es ist der gcc, der das nicht
>>> hinbekommt, weil er keine Unterstützung für mehere Speichertypen
>>> besitzt.
>>
>> GCC könnte es schon, nur ist das im avr-Teil (noch) nicht eingebaut.
>
> Ist das neu? Als ich das letzte Mal (genau wegen dieser Thematik) in den
> gcc-Eingeweiden rumgewühlt hab, hab ich da nichts gefunden.

http://gcc.gnu.org/onlinedocs/gccint/Named-Address-Spaces.html#Named-Address-Spaces

Autor: Josef D. (jogedua)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Etwas lästig ist, daß die Pindefinitionen immer zweimal gemacht werden
> müssen, je einmal für Ausgangs- und Richtungsbit.

Und ggf. ein drittes Mal für die Eingangsbits.
Das hat mich auch schon lange geärgert; man kann es vermeiden, indem man 
das Register (PORT/DDR/PIN) als Parameter ins Macro nimmt:

#define _AP_  __attribute__((__packed__))
// JD 20110709 erweitert// Access bits like variables: aus Dannegger
typedef union uBits_ut{
  // .bX   (b0, b1, ..): einzelbits, X:Bit-Nummer
  // .bXY  (b21,    ..): Gruppen von bits; X:Anzahl der Bits in der Gruppe, Y: niedrigstes Bit der Gruppe
  // .bXYZ (b301,   ..): Füllbits
  struct {uint8_t b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;} _AP_;  // Einzelbits
  struct {uint8_t b40:4, b44:4; } _AP_;            // Nibbles
  struct {uint8_t b80; } _AP_;                // ganzes Byte
#if 0 // bisher noch ungetestet:
  struct {uint8_t b20:2,  b22:2, b24:2, b26:2;} _AP_;      // Bit-Paare gerade
  struct {uint8_t b201:1, b21:2, b23:2, b25:2, b271:1;} _AP_;  // Bit-Paare ungerade;
  struct {uint8_t b30:3,  b33:3, b362:2;} _AP_;        // Bit-Tripletts0
  struct {uint8_t b301:1, b31:3, b34:3, b371:1;} _AP_;    // Bit-Tripletts1
  struct {uint8_t b302:2, b32:3, b35:3;}  _AP_;        // Bit-Tripletts2
  struct {uint8_t b401:1, b41:4, b453:3;} _AP_;    // 4erBits, beginnen bei 1 (0 und 4 s. Nibbles, oben
  struct {uint8_t b402:2, b42:4, b462:2;} _AP_;    // 4erBits, beginnen bei 2
  struct {uint8_t b403:3, b43:4, b471:1;} _AP_;    // 4erBits, beginnen bei 3
  struct {uint8_t b50:5,  b553:3;} _AP_;        // 5erBits, beginnen bei 0
  struct {uint8_t b501:1, b51:5, b562:2;} _AP_;    // 5erBits, beginnen bei 1
  struct {uint8_t b502:2, b52:5, b571:1;} _AP_;    // 5erBits, beginnen bei 2
  struct {uint8_t b503:3, b53:5;} _AP_;        // 5erBits, beginnen bei 3
  struct {uint8_t b60:6,  b662:2;} _AP_;        // 6erBits, beginnen bei 0
  struct {uint8_t b601:1, b61:6, b671:1;} _AP_;    // 6erBits, beginnen bei 1
  struct {uint8_t b602:2, b62:6;} _AP_;        // 6erBits, beginnen bei 2
  struct {uint8_t b70:7,  b771:1;} _AP_;        // 7erBits, beginnen bei 0
  struct {uint8_t b701:1, b71:7;} _AP_;        // 7erBits, beginnen bei 1
#endif
} uBits_t;

#define GLUE(a,b)  a##b // aus WinAVR-20090313\doc\avr-libc\examples\stdiodemo/hd44780.c
#define PORTBITS(reg,port,bits) ((*(volatile uBits_t*)&GLUE(reg,port)).GLUE(b,bits))
// reg=DDR/PORT/PIN, port=A/B/C/..., z.B. PORTBITS(PORT,C,1) ergibt PORTC.b1

/* Anwendung z.B. für LCD
  Nur in EINER Zeile muss man noch ändern (Portnummer und Bitnummer)
  //die DATA-Bits müssen zusammenhängend sein, 
  //  einzelne Bits können auch auf anderen Ports liegen, auch auf verschiedenen
  #define LCD_DATA(port)    PORTBITS(port, D, 44)  // h-nibble, 4 bits ab Bit 4 (4..7)
  #define LCD_RS(port)    PORTBITS(port, D, 2)  // Bit 2
  #define LCD_EN(port)    PORTBITS(port, D, 3)  // Bit 3 (Bit 0/1 belegt durch RX/TX)
  // In LcdInit:
  LCD_DATA(DDR)= 0xF;  //  4 bit als Ausgänge; das Schieben und Maskieren macht der Compiler
  LCD_RS(DDR)= 1;
  LCD_EN(DDR)= 1;
  ...
  LCD_RS(PORT)= 1;  // Ausgang auf 1
  LCD_RS(PORT)= 0;  // Ausgang auf 0
  LCD_RS(PIN)= 1;    // Ausgang togglen
  b= LCD_RS(PIN);    // Eingang lesen
  LCD_DATA(PORT)= 0x1;  
*/


Autor: sven_w (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Nein. Du kannst Speicher dafür reservieren. Zum Erzeugen einer Instanz
> gehören aber noch Konstruktoren und andere Dinge.

Das heißt ich implementiere in meiner Klasse einen Konstruktor bzw. 
Destruktor wie sont auch bei c++.
Rufe den Konstruktor aber über maloc auf ?
Kann jemand ein kurzes Beispiel geben, wie man eine Instanz über malloc 
erzeugt ?

Autor: Stefan E. (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sven_w schrieb:
> Rufe den Konstruktor aber über maloc auf ?

Nein, du implementierst die Operatoren new und delete mit Hilfe von 
malloc und free, und benutzt dann ganz normal new/delete.
void * operator new (size_t size) {

  return malloc(size);
}

void operator delete (void * ptr) {

  free(ptr);
}

Autor: Ralf G. (ralg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Nein, du implementierst die Operatoren new und delete mit Hilfe von
> malloc und free, und benutzt dann ganz normal new/delete.
Wobei jetzt noch der für 'new' und 'delete' typische Aufruf des 
Konstruktors/ Destruktors fehlt. (Ich hoffe, ich irre mich nicht.)

Autor: willibald (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> sven_w schrieb:
>> Rufe den Konstruktor aber über maloc auf ?
>
> Nein, du implementierst die Operatoren new und delete mit Hilfe von
> malloc und free, und benutzt dann ganz normal new/delete.
> void * operator new (size_t size) {
>
>   return malloc(size);
> }
>
> void operator delete (void * ptr) {
>
>   free(ptr);
> }

Aua, bloß nicht!

Wenn ein Ding "new" heißt, sollte es IMMER new-Verhalten haben, d.h. 
allozieren UND konstruieren.

Konstruieren heißt unter anderem, dass die VTable gesetzt wird, wenn das 
Objekt virtuelle Funktionen hat. Daran führt KEIN Weg vorbei, das ist 
der Overhead, den man bezahlen MUSS, wenn man C++ verwenden will. Eine 
nicht gesetzte VTable lässt das Programm todsicher krachen.

Autor: Stefan E. (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. schrieb:
> Wobei jetzt noch der für 'new' und 'delete' typische Aufruf des
> Konstruktors/ Destruktors fehlt. (Ich hoffe, ich irre mich nicht.)

willibald schrieb:
> Aua, bloß nicht!
>
> Wenn ein Ding "new" heißt, sollte es IMMER new-Verhalten haben, d.h.
> allozieren UND konstruieren.

Ich benutze C++ zwar nicht wirklich, aber ich bin mir doch ziemlich 
sicher, dass der ganze Rest vom Compiler bei der Benutzung des new dann 
selber gemacht wird. Für die Operator-Implementierung reicht das 
Allozieren. Wie sollte man auch mehr machen, wo man außer der Größe ja 
auch null Informationen über das anzulegende Objekt hat.

Autor: Nico S. (nico22)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Ich benutze C++ zwar nicht wirklich, aber ich bin mir doch ziemlich
> sicher, dass der ganze Rest vom Compiler bei der Benutzung des new dann
> selber gemacht wird.

Korrekt.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
willibald schrieb:
> Stefan Ernst schrieb:
>> sven_w schrieb:
>>> Rufe den Konstruktor aber über maloc auf ?
>>
>> Nein, du implementierst die Operatoren new und delete mit Hilfe von
>> malloc und free, und benutzt dann ganz normal new/delete.
>> void * operator new (size_t size) {
>>
>>   return malloc(size);
>> }
>>
>> void operator delete (void * ptr) {
>>
>>   free(ptr);
>> }
>
> Aua, bloß nicht!

Doch

> Wenn ein Ding "new" heißt, sollte es IMMER new-Verhalten haben, d.h.
> allozieren UND konstruieren.

Das vom C++ geforderte Verhalten des globalen operator new besteht 
darin, Speicher zu besorgen. Er muss keinen Konstruktor bedienen, das 
ist nicht seine Aufgabe. Abgesehen davon könnte er das auch gar nicht, 
weil er nicht weiß, für welches Objekt er aufgerufen wurde.

Nochmal: Die Operatoren new bzw delete haben die Aufgabe Speicher zu 
allokieren. Damit ist ihre Schuldigkeit getan. Um den Rest muss sich der 
Compiler kümmern.


Edit: zu langsam. Hab zu lange zum Lesen des kompletten Thread gebraucht 
:-)

Autor: Fabian G. (kjion) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus B. schrieb:
> Um mal wieder zum Topic zu kommen
>
> http://xpcc.sourceforge.net/

Als einer der Autoren dieses Projektes und mittlerweile großer Fan von 
C++ auf Mikrocontrollern muss ich auch mal was dazu sagen ;-)

Unter 
http://sourceforge.net/apps/trac/xpcc/browser/trunk/examples/lcd/hd44780/main.cpp 
gibt es das Beispiel mit dem LCD mal mit einem etwas anderen Ansatz.

Wir verwenden dort Macros um für jeden IO-Pin Structs mit statischen 
inline Funktionen zu erzeugen. Diese Structs kann man dann an die 
Hardware-Treiber als Template Argument übergeben.

Das Beispiel oben verlinkte Beispiel erzeugt mit einem avr-gcc 4.3.4 und 
aktivierten Optimierungen insgesamt 1610 Byte. Es ist allerdings schon 
die Luxus Variante mit Stream Operatoren zur Ausgabe von belieben 
Zahlen, virtuellen Funktionen usw.

Um den Ansatz besser vergleichen zu können habe ich mal eine minimale 
Variante zusammengestrickt, siehe Anhang (wer das selbst compilieren 
will braucht Python und SCons und muss den Wert von rootpath in der 
SConstruct Datei so anpassen, dass er auf das xpcc-Verzeichnis 
verweist). Diese Variante kann jetzt nicht mehr als einzelnen Zeichen 
und Strings auf ein Display auszugeben. Damit bleiben von den 1610 nur 
noch 584 Byte übrig.
Program:     584 bytes (0.9% Full)
(.text + .data + .bootloader)

Data:         18 bytes (0.4% Full)
(.data + .bss + .noinit)

Und so sieht das Ganze dann aus:
#include <xpcc/architecture.hpp>
#include "hd44780_minimal.hpp"

// define the pins used by the LCD
namespace lcd
{
  GPIO__OUTPUT(E, C, 6);
  GPIO__OUTPUT(Rw, C, 5);
  GPIO__OUTPUT(Rs, C, 4);
  GPIO__NIBBLE_LOW(Data, C);
}

// create a LCD object
xpcc::Hd44780Minimal< lcd::E, lcd::Rw, lcd::Rs, lcd::Data > display(20, 4);

int
main()
{
  // The LCD needs at least 50ms after power-up until it can be
  // initialized.
  xpcc::delay_ms(50);
  
  display.initialize();
  display.setCursor(0, 0);
  
  // write the standard welcome message ;-)
  display.write("Hello World!\n");
  
  while (1)
  {
  }
}

Damit spart man sich ganz herum gewurschtele mit dem Preprozessor und 
kann einfach und übersichtlich die Pins einstellen.

Ganz fair ist der Vergleich aber nicht, da dort schon eine optimierte 
Variante zur Ansprechen des Port-Nibble verwendet wird. Es ist aber 
nicht schwierig beliebige Pins auch für die Daten zu verwenden. Dazu 
muss man das Programm nur folgendermaßen abändern (Pins zufällig 
gewählt):
namespace lcd {
  GPIO__OUTPUT(E, C, 6);
  GPIO__OUTPUT(Rw, C, 5);
  GPIO__OUTPUT(Rs, C, 4);
  
  GPIO__IO(Data0, A, 4);
  GPIO__IO(Data1, B, 1);
  GPIO__IO(Data2, C, 7);
  GPIO__IO(Data3, D, 2);
  
  typedef xpcc::gpio::Nibble<Data3, Data2, Data1, Data0> Data;
}

Der Rest bleibt gleich. Das zeigt so ein bisschen die Flexiblität die 
dieser Ansatz bietet ;-)
Damit erhöht sich der Verbrauch dann auf
Program:     658 bytes (1.0% Full)
(.text + .data + .bootloader)

Data:         18 bytes (0.4% Full)
(.data + .bss + .noinit)

Den gleichen Code kann man übrigens ohne große Änderungen (Pins anders 
und Takt für GPIO aktivieren) auch direkt für ARM7 compilieren ;-)

Grüße
Fabian

Autor: Fabian G. (kjion) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
... und jetzt sogar mit Anhang.

Autor: sven_w (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
willibald schrieb:
> Konstruieren heißt unter anderem, dass die VTable gesetzt wird, wenn das
>
> Objekt virtuelle Funktionen hat. Daran führt KEIN Weg vorbei, das ist
>
> der Overhead, den man bezahlen MUSS, wenn man C++ verwenden will. Eine
>
> nicht gesetzte VTable lässt das Programm todsicher krachen.Beitrag melden | 
Bearbeiten | Löschen |

Was ist eine VTable ?

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Virtuelle Funktionen werden meistens indirekt aufgerufen. Teil eines 
Objekts einer Klasse mit virtuellen Funktionen ist ein Pointer auf eine 
Tabelle mit Pointern auf Funktionen. Die VFT (virtual function table) 
oder vtable.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ein C++ Beispiel (ich lass CTor und DTor weg)
class A
{
public
  virtual void foo()   { printf( "Dies ist A\n" ); }
};

class B : public A
{
public:
  virtual void foo()   { printf( "Dies ist B\n" ); }
};


int main()
{
  B  myB;
  A* pA = &B;

  pA->foo();
}

Der Aufruf pA->foo() landet bei B::foo

Wie wird das unter der Decke implementiert

Jede Klasse hat eine VTable, in der Pointer zu den virtuellen Funktionen 
sind. Die VTable für A hat also einen Pointer zu A::foo. Die VTable für 
B hat einen Funktionspointer zu B::foo

Jedes Objekt wiederrum besitzt einen Pointer zu der für es gültigen 
VTable. myB hat also einen Pointer auf die B-VTable, weil myB ja ein B 
Objekt ist.

Wenn der Compiler den Funktionsaufruf pA->foo() übersetzt, dann macht er 
das wegen der Virtualität der Funktion foo so:

Mittels des POinters im Objekt den Pointer zur VTable holen (der Pointer 
steht in allen Objekten immer an gleicher Stelle, normalerweise am 
Objektanfang im Speicher). Mit dem jetzt bekannten VTable Pointer holt 
er den zum Objekt gehörenden Funktionspointer und mit diesem wiederrum 
wird dann die Funktion aufgerufen.

So wird erreicht, dass je nachdem auf welches Objekt (welcher 
Klassentyp) pA tatsächlich zeigt, das Objekt selbst steuert, dass seine 
zugehörige Funktion aufgerufen wird.

In C würde man das so machen:
typedef void (*voidPtr)( void );

// die generische VTable, die für Klasse A bz. B zuständig ist
struct VTable
{
  voidPtr   fooFnct;
};

// die beiden virtuellen Funktionen
void foo_A()
{
  printf( "Dies ist A\n" );
}

void foo_B()
{
  printf( "Dies ist B\n" );
}

// für 'class' A bzw B jeweils eine VTable anlegen und mit den korrekten
// Funktionspointern befüllen
struct VTable VTable_A  { foo_A };
struct VTable VTable_B  { foo_B };

// dann brauchen wir noch die Klassen selber
struct class_A
{
  struct VTable* pVTable;
}

struct class_B
{
  struct VTable* pVTable;
}

// alle Zutaten beieinander. Jetzt kanns losgehen
int main()
{
  struct class_B myB = { &VTable_B };
  struct class_A * pA = (struct class_A*) &myB;

  //pA->foo()
  struct VTable* pVTab = pA->pVTable;  // geht deshalb weil pVTable sowohl
                                       // in struct class_A als auch in
                                       // struct class_B denselben Offset vom
                                       // Objektbeginn weg hat.
  voidPtr foo = pVTab->fooFnct;
  (*foo)();
}

(DIe Behandlung des this Pointers hab ich mir in der C Variante 
geschenkt)

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> Nochmal: Die Operatoren new bzw delete haben die Aufgabe Speicher zu
> allokieren. Damit ist ihre Schuldigkeit getan. Um den Rest muss sich der
> Compiler kümmern.
Und bei so einem selbstgebastelten 'new' und 'delete' weiß der Compiler 
plötzlich, daß er jetzt fix noch Konstruktor und Destruktor aufrufen 
muss?

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Karl Heinz Buchegger schrieb:
>> Nochmal: Die Operatoren new bzw delete haben die Aufgabe Speicher zu
>> allokieren. Damit ist ihre Schuldigkeit getan. Um den Rest muss sich der
>> Compiler kümmern.
> Und bei so einem selbstgebastelten 'new' und 'delete' weiß der Compiler
> plötzlich, daß er jetzt fix noch Konstruktor und Destruktor aufrufen
> muss?

Der Compiler weiss, dass er

   A = new Irgendwas;

so übersetzen muss:

   globalen operator new aufrufen, um den Speicher zu besorgen
   auf diesem Speicher, dann den Konstruktor loslassen.


In den ersten Teil kannst du eingreifen, in dem du selbst einen globalen 
operator new schreibst. Der zweite Teil ist obligatorisch und wird vom 
Compiler immer gemacht; da kannst du nicht eingreifen. Schliesslich gibt 
es da ja auch viele Möglichkeiten, je nachdem welche Konstruktoren 
überhaupt verfügbar sind.

'Verkompliziert' wird das ganze dann noch dadurch, dass jede Klasse für 
sich wieder die Möglichkeit hat, einen klassenspezifischen operator new 
zu implementieren.
D.h. der Compiler sieht zuerst in der Klasse selber nach, ob es dort 
einen operator new gibt, der den Speicher besorgt. Findet er dort 
nichts, dann kommt der globale operator new zum Zug.

Und wieso 'plötzlich'? Klar weiß das der Compiler. Warum auch nicht? Er 
weiß ja auch, dass bei delete ein Destruktor aufgerufen werden muss. Da 
ist dann dasselbe Spielchen umgekehrt. operator delete kümmert sich um 
den Speicher und der Compiler darum, dass Destruktor und operator delete 
in dieser Reihenfolge aufgerufen wird.

Construktor bzw. Destruktor haben doch nichts damit zu tun, wo der 
Speicher herkommt, in dem das Objekt erzeugt wird.

Autor: Nico S. (nico22)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Und bei so einem selbstgebastelten 'new' und 'delete' weiß der Compiler
> plötzlich, daß er jetzt fix noch Konstruktor und Destruktor aufrufen
> muss?

Korrekt. Und warum plötzlich? Das ist eben so definiert.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
willibald schrieb:
> Wenn ein Ding "new" heißt, sollte es IMMER new-Verhalten haben, d.h.
> allozieren UND konstruieren.

Wenn du nicht weißt, wie man einen eigenen Operator new definiert, dann 
solltest du nicht versuchen, das anderen zu erklären.

> Konstruieren heißt unter anderem, dass die VTable gesetzt wird, wenn das
> Objekt virtuelle Funktionen hat. Daran führt KEIN Weg vorbei, das ist
> der Overhead, den man bezahlen MUSS, wenn man C++ verwenden will.

Es ist der Overhead, den man bezahlen muß, wenn man Polymorphie in C++ 
nutzen will. Eine Klasse ohne virtuelle Memberfunktionen hat auch keine 
vtable. Und wenn man in C Polymporphie "zu Fuß" implementiert, wird man 
dadurch den gleichen Overhead bekommen.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> Der Compiler weiss, dass er
>
>    A = new Irgendwas;
>
> so übersetzen muss:
>
>    globalen operator new aufrufen, um den Speicher zu besorgen
>    auf diesem Speicher, dann den Konstruktor loslassen.
Aha, also das klingt so als ob 'new' und 'delete' dem AVR_C++Compiler 
zwar bekannt sind (so als Wörter), er aber den entsprechenden Befehl 
dazu nicht kennt? Und wenn man Speicher reserviert mit einem 
'new'-Operator dann erinnert der sich auch den Konstruktor aufzurufen? 
Hmm, Zauberei.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:

> vtable. Und wenn man in C Polymporphie "zu Fuß" implementiert, wird man
> dadurch den gleichen Overhead bekommen.


Das ist nämlich der springende Punkt, der gerne übersehen wird. 
Natürlich kostet polymorphes Verhalten etwas. Aber die Alternativen 
kosten auch etwas (meistens mehr) und im Zweifelsfall überlass ich das 
alles lieber dem Compiler, als dass ich selbst an diese fehlerträchtige 
Arbeit gehe. Und Polymorphismus Marke Eigenbau IST fehlerträchtig, egal 
ob mit VTable oder switch/case Typ-Verteilern (die sind noch 
unflexibler).

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Karl Heinz Buchegger schrieb:
>> Der Compiler weiss, dass er
>>
>>    A = new Irgendwas;
>>
>> so übersetzen muss:
>>
>>    globalen operator new aufrufen, um den Speicher zu besorgen
>>    auf diesem Speicher, dann den Konstruktor loslassen.
> Aha, also das klingt so als ob 'new' und 'delete' dem AVR_C++Compiler
> zwar bekannt sind (so als Wörter),

natürlich. Das sind Schlüsselwörter der Sprachdefinition

> er aber den entsprechenden Befehl
> dazu nicht kennt?

Auch das weiß er. Er weiß, dass er im Zuge der Behandlung des 
Schlüsselwortes 'new', den operator new aufrufen muss, der eine ganz 
normale Funktion ist.
Nur muss eben irgendwer irgendwann einmal diese Funktion geschrieben 
haben.

> Und wenn man Speicher reserviert mit einem
> 'new'-Operator dann erinnert der sich auch den Konstruktor aufzurufen?
> Hmm, Zauberei.


Dein Denkfehler:

du verwechselst die Schlüsselwörter 'new' und 'delete' mit den 
Operatoren new und delete.

Das Schlüsselwort new ist in

   a = new irgendwas;

nicht synonym damit, dass da nur der Operator new aufgerufen wird. Da 
passiert schon noch mehr. Drum sieht ja auch die Syntax nicht wie ein 
Funktionsaufruf aus :-)

Was stört dich an der Sache?
Bei
   for( i = 0; i < 8; ++i )
weiß der Compiler ja auch, was er mit den einzelnen Teilen zu machen 
hat.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> du verwechselst die Schlüsselwörter 'new' und 'delete' mit den
> Operatoren new und delete.
Also Verwechslung. Ist das nachzuvollziehen, dass ich das verwechselt 
habe oder fehlt mir da einfach der Durchblick?
Ich versuche zu verstehen, wozu man sich einen Operator 'new' bastelt, 
von dem man nur ein bisschen Speicher zurückbekommt. Ich würde da mehr 
erwarten. Oder ist in AVR-C++ 'new' zwar implementiert aber das 
Speicherallozieren wurde 'vergessen'? ...???

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:

> Also Verwechslung. Ist das nachzuvollziehen, dass ich das verwechselt
> habe oder fehlt mir da einfach der Durchblick?

Solange man da selbst niemals ran muss (um zb Chunk Allokierung aus 
Performance Gründen zu machen), ist der operator new ein Abschnitt im 
Lehrbuch das man liest und gleich wieder vergisst.

> Ich versuche zu verstehen, wozu man sich einen Operator 'new' bastelt,
> von dem man nur ein bisschen Speicher zurückbekommt.

Das ist seine Aufgabe. Mehr muss er nicht tun.
Und 'nur ein bischen Speicher zurükbekommt' kann je nach System schon 
eine anspruchsvolle Aufgabe für sich selbst sein.
Du kannst zb in operator new deine eigene Speicherverwaltung 
implementieren, die völlig losgelöst von dem agiert, was dir das 
Betriebssystem aufs Auge drückt.
Oder du kannst natürlich auch einfach malloc bemühen. Soll sich doch 
malloc mit dem BS rumstreiten :-)

Auf einem AVR könnte man zb ein 100 Byte großes Array als dynamischen 
Speicher abstellen, wenn man weiß, dass man in Summe niemals mehr als 
100 Bytes dynamisch allokiert. Dann hat man dort eine Möglichkeit, wie 
man den kompletten Speicherverbrauch des Programms trotz dynamischer 
Allokierung schon während des Compilierens feststellen kann.

Oder ....

Möglichkeiten gibt es viele.

> Ich würde da mehr
> erwarten. Oder ist in AVR-C++ 'new' zwar implementiert aber das
> Speicherallozieren wurde 'vergessen'? ...???

Ob Absicht oder nicht kann ich nicht sagen. Tatsache ist, dass es in der 
Auslieferversion keine Implementierung dafür gibt.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> Ob Absicht oder nicht kann ich nicht sagen. Tatsache ist, dass es keine
> Implementierung dafür gibt.
Ich bastle mir zu einem kompletten 'new' noch den Operator 'new' dazu 
und dann funktioniert alles so, wie man es gewohnt ist? Ich kann's gar 
nicht glauben!

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Aha, also das klingt so als ob 'new' und 'delete' dem AVR_C++Compiler
> zwar bekannt sind (so als Wörter), er aber den entsprechenden Befehl
> dazu nicht kennt?

new und delete sind Schlüsselwörter. Somit kennt g++ die immer. Beim 
avr-g++ fehlt lediglich die dahinterstehende Implementation. Wenn du 
also new einfach so benutzt, bekommst du eine Fehlermeldung vom Linker.

Ralf G. (ralg) schrieb:
> Ich versuche zu verstehen, wozu man sich einen Operator 'new' bastelt,
> von dem man nur ein bisschen Speicher zurückbekommt. Ich würde da mehr
> erwarten.

Aber genau das ist seine Aufgabe. Was sollte er mehr machen?

> Oder ist in AVR-C++ 'new' zwar implementiert aber das Speicherallozieren
> wurde 'vergessen'? ...???

Es ist Teil einer Bibliothek, die sich für AVR nicht bauen läßt. Darum 
fehlt unter anderem eben diese Funktion (was z.B. auch fehlt, sind 
Exceptions, welche der standardmäßige Operator new übrigens auch 
benötigt). Es hat sich eben noch keiner hingesetzt und darum gekümmert, 
daß es das für avr gibt.

Karl Heinz Buchegger schrieb:
> du verwechselst die Schlüsselwörter 'new' und 'delete' mit den
> Operatoren new und delete.

Genauer gesagt den new-Ausdruck mit Operator new. In der ISO-Norm werden 
die so bezeichnest ("new-expression" und "operator new"). Hier ein 
Auszug:

"Objects can be created dynamically during program execution (1.9), 
using new-expressions (5.3.4), and destroyed using delete-expressions 
(5.3.5). A C++ implementation provides access to, and management of, 
dynamic storage via the global allocation functions operator new and 
operator new[] and the
global deallocation functions operator delete and operator delete[]."

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Karl Heinz Buchegger schrieb:
>> Ob Absicht oder nicht kann ich nicht sagen. Tatsache ist, dass es keine
>> Implementierung dafür gibt.
> Ich bastle mir zu einem kompletten 'new' noch den Operator 'new' dazu
> und dann funktioniert alles so, wie man es gewohnt ist? Ich kann's gar
> nicht glauben!

Du brauchst auch noch operator delete :-)

Ja. Was verblüfft dich daran jetzt?
Sieh den op new einfach als Phase an, die bei einer Objektkonstruktion 
durchlaufen wird und bei der dir C++ die Möglichkeit gibt, dort 
einzugreifen.
Mehr steckt da nicht dahinter. Das ist die Absicht hinter dem Ganzen.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:

> Genauer gesagt den new-Ausdruck mit Operator new. In der ISO-Norm werden
> die so bezeichnest ("new-expression" und "operator new"). Hier ein
> Auszug:

Richtig.
Ist schon lange her, dass ich das letzt mal in der Ecke im Standard war.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> Ja. Was verblüfft dich daran jetzt?
Wenn ich schreibe:
a=new b;
dann wird Speicher reseviert und der Konstruktor für den Typ b 
aufgerufen.
Das 'Bastel'-new holt sich doch nur den Speicher. Das ist doch 
'Vortäuschung falscher Tatsachen'?

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ach so:
'delete' machen wir später :-)

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Karl Heinz Buchegger schrieb:
>> Ja. Was verblüfft dich daran jetzt?
> Wenn ich schreibe:
> a=new b;
> dann wird Speicher reseviert und der Konstruktor für den Typ b
> aufgerufen.
> Das 'Bastel'-new holt sich doch nur den Speicher. Das ist doch
> 'Vortäuschung falscher Tatsachen'?


Wie oft denn noch?


Objektkonstruktion ist in 2 Phasen gegliedert.

   Speicher besorgen
   Konstruktor aufrufen.

Bei Speicher besorgen gibt es meherer Möglichkeiten
   das kann Speicher vom Stack sein
   das kann Speicher sein, der mittels Placement new vom Aufrufer
        gestellt wird, wo sich der Compiler also überhaupt nicht
        darum kümmern muss.
   das kann Speicher sein, den der operator new besorgen muss
       (nämlich dann, wenn die Objektkonstruktion über eine
        normale new Expression erzwungen wird)

Konstruktor aufrufen kann ebenfalls vieles sein
   das kann der Default konstruktor sein
   das kann ein Copy Konstruktor sein
   das kann ein benutzerdefinierter Konstruktor mit einer
   nicht näher (im Sprachstandard) festgelegten Anzahl von
   Paramatern und Datentypen sein.

(Und das ganze gibt es dann auch noch in Array-Versionen)

Willst du wirklich für all die unterschiedlichen Konstruktoraufrufe 
jeweils einen eigenen op new schreiben?


Das 'Bastel-New' ist der Teil, der im Fall "Speicher Besorgen und zwar 
aufgrund einer Objektkonstruktion mittels einer new-Expression" ganz 
offiziell dafür zuständig ist, den Speicher zu besorgen. Das ist NICHT 
gebastelt! Das ist ganz offiziell so vorgesehen und im Sprachstandard 
dokumentiert. Was ist denn schon dabei, wenn der Sprachstandard da einen 
Sytemimplementieren dazu zwingt diesen Operator zu schreiben? Wenn du 
qsort verwenden willst, musst du doch auch eine Vergleichsfunktion 
bereitstellen, die für qsort eine bestimmte wohldefinierte Aufgabe 
erledigt. Und hier ist das nichts anderes. In bestimmten Fällen benutzt 
der Compiler einen Aufruf einer von irgendjemand bereitsgestellten 
'Funktion' namens "operator new" um den Teil 'Speicher organsieren 
während einer Objektkonstruktion' in bestimmten Fällen erledigen zu 
lassen. Hat der Compiler den Pointer zum Speicher erst mal in Händen, 
dann ruft er auf diesem Speicher den anhand gewisser Kriterien 
ausgewählten Konstruktor auf. Das hat aber nichts mehr mit dem op new zu 
tun. Der hat seinen Job dann schon längst erledigt.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich glaube, er hätte weniger Probleme damit, wenn die Funtionen karl() 
und franz() statt operator new() und operator delete() hießen.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl Heinz Buchegger schrieb:
> Willst du wirklich für all die unterschiedlichen Konstruktoraufrufe
> jeweils einen eigenen op new schreiben?
Da schreiben wir aneinander vorbei. Ich weiß ja jetzt, dass ich das 
Schlüsselwort mit dem Operator gleichgesetzt habe. Also, ein 
'new'-Aufruf auf dem AVR führt zur Fehlermeldung, weil es dazu keinen 
Code gibt. Damit man 'new' trotzdem verwenden kann, macht man diese 
'new-malloc-Speicheranmeldung' draus. Aber eben nicht mehr, weil das 
nicht ins Programm gehöhrt. Da muss sich der Compiler drum kümmern. Nur 
kann das der AVR-Compiler nicht, weil man es ihm nicht beigebracht hat.
Ist das bis hierher richtig?
Falls ja: Wozu dann dieses Hilfs-new, wenn man (wie auf anderen 
Systemen), mehr erwarten müsste?
Falls nein: Ich brauch 'ne Pause!

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Ich glaube, er hätte weniger Probleme damit, wenn die Funtionen karl()
> und franz() statt operator new() und operator delete() hießen.
Genau, das ist die Antwort. [ karl() und rolf() natürlich :-) ]
Deswegen:
Ralf G. (ralg) schrieb:
> Das 'Bastel'-new holt sich doch nur den Speicher. Das ist doch
> 'Vortäuschung falscher Tatsachen'?
Und meine Bedenken:
Da tauchen in einem Quelltext 'new' und 'delete' auf, und jeder wundert 
sich, wieso das überhaupt sein kann! Naja, wahrscheinlich nur ich...

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:

> Falls nein: Ich brauch 'ne Pause!


Das denk ich auch.

Du denkst viel zu kompliziert.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:

> Da schreiben wir aneinander vorbei. Ich weiß ja jetzt, dass ich das
> Schlüsselwort mit dem Operator gleichgesetzt habe. Also, ein
> 'new'-Aufruf auf dem AVR führt zur Fehlermeldung, weil es dazu keinen
> Code gibt. Damit man 'new' trotzdem verwenden kann, macht man diese
> 'new-malloc-Speicheranmeldung' draus. Aber eben nicht mehr, weil das
> nicht ins Programm gehöhrt. Da muss sich der Compiler drum kümmern. Nur
> kann das der AVR-Compiler nicht, weil man es ihm nicht beigebracht hat.
> Ist das bis hierher richtig?

Der Compiler kann das alles.
Aber in den Systemlibraries findet der Linker keine Implementierung für 
die Funktion 'operator new' (Ein Operator ist auch nur eine Funktion), 
die vom Compiler zu Hilfszwecken (eben dem Besorgen von Speicher) 
benutzt wird, wenn du das new Schlüsselwort benutzt.

> Falls ja: Wozu dann dieses Hilfs-new, wenn man (wie auf anderen
> Systemen), mehr erwarten müsste?

Man erwartet eben nicht mehr!
Man muss nicht mehr erwarten!

diese implementierung von new macht genau das, was sie tun soll. Nicht 
mehr und nicht weniger.

Autor: Stefan E. (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Da muss sich der Compiler drum kümmern. Nur
> kann das der AVR-Compiler nicht, weil man es ihm nicht beigebracht hat.

Wo hast du das denn jetzt raus gelesen? Klar kann er das.

Autor: Ralf G. (ralg) (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Okay, also: Pause.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OK. Gedankenswitch

Stell dir einfach vor die Funktion hiesse nicht "operator new" sondern 
"operator allocateMemory".

That's it. Das ist ihre Aufgabe. Es ist eine Hilfsfunktion für den 
Compiler, die er aufrufen kann, wenn er an irgendeiner Stelle im Code 
eine Anforderung zur dynamischen Objekterzeugung vorfindet. Er benutzt 
diese Hilfsfunktion um zunächst den Speicher zu reservieren in dem dann 
in weiterer Folge das Objekt erzeugt wird.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ralf G. (ralg) schrieb:
> Falls ja: Wozu dann dieses Hilfs-new, wenn man (wie auf anderen
> Systemen), mehr erwarten müsste?

Man erwartet aber nicht mehr, weder auf dem AVR, noch auf irgendeinem 
anderen System. Diese Möglichkeit, einen eigenen operator new zu 
defnieren, ist keine Ausgeburt von avr-g++, sondern ist in der ISO-Norm 
zu C++ genau so festgelegt und wird von jedem konformen Compiler auch 
genau so unterstützt. Der einzige Unterschied ist, daß es nach 
ISO-Definition eigentlich schon einen vordefinierten operator new geben 
müßte, der automatisch einspringt, wenn man nicht selbst einen 
definiert. Dieser vordefinierte operator new fehlt beim avr-g++, also 
muß man sich immer selbst einen definieren.

Ralf G. (ralg) schrieb:
> Rolf Magnus schrieb:
>> Ich glaube, er hätte weniger Probleme damit, wenn die Funtionen karl()
>> und franz() statt operator new() und operator delete() hießen.
> Genau, das ist die Antwort. [ karl() und rolf() natürlich :-) ]

Ja, natürlich ;-)

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielleicht zur Klarstellung:
C++ will nicht ermöglichen, das Verhalten der Sprache komplett 
umkrempeln zu lassen. Beispielsweise steht nicht zur Wahl, ob man den 
ctor/dtor-Mechanismus aushebeln will oder ob new vielleicht gar nicht 
Objekte erzeugt, sondern stattdessen Bier bestellt.

Es gibt aber in dem vorgegebenen Mechanismus einige Stellen, an denen 
man in Grenzen eingreifen kann.
Der eine Punkt ist: wie soll ein Objekt initialisiert werden oder 
aufgelöst?
Dazu überschreibt man einen Konstruktor bzw. Destruktor.
Der andere Punkt ist eben: woher nimmt man den Speicher?
Je nach System ist vielleicht nicht der ganze Speicher gleichwertig. Bei 
manchen AVR kann man z.B. zwischen schnellerem internen und ggf. 
externem Speicher wählen, oder auf anderen Systemen mit shared memory 
oder memory mapped files spielen. Das wird ermöglicht, indem man 
operator new umschreibt.

Das alles ändert aber nichts am vorgegeben Rahmen.

Bei anderen Operatoren ist es ähnlich (soweit man das überhaupt 
vergleichen kann): Man kann neu definieren, was sie im Detail machen, 
aber weder kann man eine Addition mit 5 Operanden bauen, noch die 
Rangfolge der Operatoren ändern oder neue schaffen.

Autor: Ralf G. (ralg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
(1) willibald schrieb:
> Aua, bloß nicht!
>
> Wenn ein Ding "new" heißt, sollte es IMMER new-Verhalten haben, d.h.
> allozieren UND konstruieren.

(2) Karl Heinz Buchegger schrieb:
> Der Compiler weiss, dass er
>
>    A = new Irgendwas;
>
> so übersetzen muss:
>
>    globalen operator new aufrufen, um den Speicher zu besorgen
>    auf diesem Speicher, dann den Konstruktor loslassen.

(3) Rolf Magnus schrieb:
> Ich glaube, er hätte weniger Probleme damit, wenn die Funtionen karl()
> und franz() statt operator new() und operator delete() hießen.

Mit neuem Erkenntnisstand, habe ich mir noch mal alles durchgelesen :-)
Das waren, nur z.B., so die Dinge, die mich verwirrten:
zu (1): so hatte ich mir das auch vorgestellt
zu (2): das meinte ich, was 'new' können soll [durch 
'Schlüsselwort/Operator-Gleichsetzung' (sehr) unklar ausgedrückt]
zu (3): klingt ja wieder wie (1) nur mit anderen Namen, fehlt dann nicht 
der Konstruktor/Destruktor-Aufruf?

Ich entscheide mich jetzt für (2) :)

Autor: Nico S. (nico22)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Ralf,

Ralf G. schrieb:
> Ich entscheide mich jetzt für (2) :)

hier etwas Pseudocode, wie der Compiler aufgebaut sein könnte.
TYPE *create_new_object() {
    TYPE *ptr;
    ptr = operator new(sizeof(TYPE));
    initialize(ptr); // Membervariablen einrichten
    ptr->TYPE(); // Constructor

    return ptr;
}

Diese Routine ist im Prinzip fest im Compiler verdrahtet und das 
Einzige, was du daran ändern kannst, ist der operator new, der allein 
die Aufgabe hat, ein n Byte großes Speicherstück zu besorgen.

Genau diese Speicherholfunktion fehlt im avr-g++; den Ablauf, ein Objekt 
in C++ zu erzeugen, kennt der Compiler jedoch.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielleicht packen wir's mal ganz anders an:

Wenn ich eine Klasse X habe, und in einer Funktion folgendes schreibe:
X x(100);
wird zuerst automatischer (lokaler) Speicher für das Objekt reserviert, 
dann wird sein Konstruktor aufgerufen und diesem der Wert 100 übergeben.
Wenn ich stattdessen folgendes schreibe:
X* x = new X(100);
passiert quasi das gleiche, bis auf daß der Speicher dynamisch über den 
Operator new reserviert wird.

Bei der Erzeugung eines Objekts, dessen Typ einen Konstruktor hat, wird 
dieser immer implizit aufgerufen. Das kann man nicht verhindern, und man 
kann man ihn auch nicht explizit aufrufen.

Autor: Ralf G. (ralg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Wenn ich eine Klasse X habe, und in einer Funktion folgendes schreibe:
> X x(100);
Das Beispiel hatte ich bis heute früh immer ignoriert (Weil selten bis 
gar nicht von mir so angewendet). Und deswegen die 
Verwechslung/Zuordnung/Zusammenfassung/... von 'new' als Schlüsselwort 
und Operator. Als mir's dann einfiel, hatte ich auch den Gedanken, dass 
es so funktionieren müsste, wie
Nico Sch. schrieb:
> Genau diese Speicherholfunktion fehlt im avr-g++; den Ablauf, ein Objekt
> in C++ zu erzeugen, kennt der Compiler jedoch.

Autor: Karl H. (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:

> Der andere Punkt ist eben: woher nimmt man den Speicher?
> Je nach System ist vielleicht nicht der ganze Speicher gleichwertig. Bei
> manchen AVR kann man z.B. zwischen schnellerem internen und ggf.
> externem Speicher wählen, oder auf anderen Systemen mit shared memory
> oder memory mapped files spielen. Das wird ermöglicht, indem man
> operator new umschreibt.

Eine andere Möglichkeit, wozu so etwas gerne benutzt wird, sind Debug 
Hilfen in Form von Leak Detektoren.
Der op new führt eine Liste aller Allokierungen mit, der op delete 
entfernt dann freigegebene wieder. Wenn am Programmende etwas in der 
Liste übrig bleibt, dann hat man Memory Leaks.
Gerne wird das ganze dann auch mit Guard Bytes gekoppelt. Da allokiert 
der op new einfach etwas mehr Speicher als angefordert, platziert den 
angeforderten Speicher in die Mitte hinein und füllt die restlichen 
Bytes mit einem bekannten Bytemuster auf. Der op delete prüft bei der 
Freigabe, ob diese Guard Bytes noch das bekannte Muster aufweisen. Wenn 
nicht ... dann hat man meistens auf ein Array Out Of Bounds geschrieben.

Autor: Ralf G. (ralg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jetzt, wo ich weiß worum es geht :-) könnte man ja auf einem AVR 
speicherplatzsparend bei new/delete vielleicht sogar auf malloc()/free() 
verzichten und sich mit dem Operator(!) ;-) 'new' nur einen Pointer auf 
einen vorher reservierten Speicherbereich holen, den sich bestimmte 
Objekte (die natürlich nicht gleichzeitig existieren dürfen) teilen.
-> über 500Byte Flash und einige Byte SRAM gespart.

Autor: Klaus W. (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
absolut richtig, für sowas ist eigene Speicherverwaltung gedacht

Autor: Michael W. (risaak)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Marcus,

anbei ein kleines C++-Programm für den ATmega128 (Nibo).
Es ist mein erster Test in C++ und ich bin froh, dass es funktioniert.

Hoffe es hilft Dir weiter :-)

Möchte mehr über C++ auf AVR lernen und mich austauschen.

Gruß
Michael

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.