Hallo Zusammen,
um auf einem AVR C++ zu verwenden gibt es für AVR-Studio eine schöne
Anleitung, wie man es einstellen muss, damit man c++ Programme
compilieren kann:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=59453
Da der AVR-GCC von sich aus keinen 'new' und 'delete' Operator hat, wird
in dem Beitrag auch aufgeführt, wie diese künstlich herzustellen sind:
1
void * operator new(size_t size)
2
{
3
return malloc(size);
4
}
5
6
void operator delete(void * ptr)
7
{
8
free(ptr);
9
}
Jetzt meine Frage: kann ich damit wirklich neue Objekte erzeugen?
( Bitte die Diskussion ob C++ auf einem AVR Sinn macht, nicht beginnen )
Gruß,
chris
> Jetzt meine Frage: kann ich damit wirklich neue Objekte erzeugen?
Ja, bis das RAM alle ist. Und das ist schnell alle.
> ( Bitte die Diskussion ob C++ auf einem AVR Sinn macht, nicht beginnen )
Ja, ja, in einem Diskussionsforum eine Diskussion vermeiden wollen. Wir
sind nicht deine Angestellten, also viel Glück mit dem Versuch.
Hier habe ich mal ein kleines Beispielprogramm erstellt. Es ergibt sich
dabei aber folgendes Problem:
Dieser Code funktioniert
1
CRectangle rect(1,1);
2
int x=rect.get_x();
eine neues Rechteck Objekt lässt sich mit 'new' auch erzeugen (
zumindest meckert der Compiler nicht )
1
CRectangle *r=new CRectangle(11,12);
Wenn ich allerdings auf die Funktion get_x() zugreifen will ..
1
x=*r.get_x();
meckert der Compiler:
../chCppTest.cpp:45: error: request for member 'get_x' in 'r', which is
of non-class type 'CRectangle*'
Was mache ich falsch?
Hier ist mein kleines Beispielprogramm:
1
#include <stdlib.h>
2
3
__extension__ typedef int __guard __attribute__((mode (__DI__)));
4
5
extern "C" int __cxa_guard_acquire(__guard *);
6
extern "C" void __cxa_guard_release (__guard *);
7
extern "C" void __cxa_guard_abort (__guard *);
8
9
int __cxa_guard_acquire(__guard *g) {return !*(char *)(g);};
chris schrieb:
> Was mache ich falsch?
Ich halte es schon fuer falsch, C auf einem Mikrokontroller zu lernen,
aber C++?
Du musst erst den Pointer dereferenzieren (*r), dann darauf zugreifen:
(*r).get_x(). Alternativ nimmst du r->get_x().
>> Was mache ich falsch?>Ich halte es schon fuer falsch, C auf einem Mikrokontroller zu lernen,>aber C++?
Peter, Du kennst Dich bestimmt besonders gut aus, kannst Du mir die
Zeilen erkären?
Diese Zeilen musst du als C++-Lernender auch nicht verstehen.
Der GCC benötigt diese Methoden scheinbar zur Laufzeit innerhalb der
C++-Umgebung. Genau kann dir dies wohl nur jemand erklären, der die
Quellen des GCC kennt.
Wichtig ist es zu wissen, dass solche Geschichten stets
Compiler-spezifisch sind. So wie GCC "Nested Functions" unterstützt, ist
eben hier dieser zusätzliche Aufwand erforderlich.
in eine eigene Datei mem.cpp und gibst die einfach zu jedem Projekt mit
dazu. Dann bist du den Teil los. An dieser Stelle ist tatsächlich "Aus
den Augen, aus dem Sinn" die beste Lösung.
> Wichtig ist es zu wissen, dass solche Geschichten stets> Compiler-spezifisch sind. So wie GCC "Nested Functions" unterstützt, ist> eben hier dieser zusätzliche Aufwand erforderlich.
Wenn Namen nicht nur Schall und Rauch sind, würde ich sagen, dass sich
die Runtime Guard-Bytes vor/hinter den Objekten einrichtet, um zb
Arrayoverflows möglicherweise erkennen zu können. Ist aber nur anhand
der Funktions-Namen geraten.
chris schrieb:
>>Ich halte es schon fuer falsch, C auf einem Mikrokontroller zu lernen,>>aber C++?> Peter, Du kennst Dich bestimmt besonders gut aus, kannst Du mir die> Zeilen erkären?
Nein, leider nicht. Ich habe mir schon vor vielen Jahren angewoehnt,
meinen Code einfach zu halten. Und das klappt auch in 99% der Faelle.
Der Nachteil ist leider, dass man dann nur selten ueber solche
Spezialitaeten stolpert und in Runden, der mehr ueber die Sprache
diskutieren, als sie zu benutzen, schnell mal inkompetent wirkt ;-)
Vielen Dank für eure Antworten.
Vielleicht sollten wir doch einmal eine kleine Diskussion zum Thema C++
auf dem AVR beginnen.
Auf die Idee c++ auf dem AVR zu verwenden bin ich bei der Betrachtung
der Arduion TWI Biblithek gekommen ( siehe Anhang ). Meiner Meinung nach
ist es nur durch die Verwendung des C++ Compilers möglich, einen so
einfach zu verstehenden Code für Leute die nur den Treiber benutzen
wollen zu generieren.
Mein Ziel ist es jetzt, diese Treiberbibliothek nicht mit der Arduino
Entwicklungsoberfläche zu kompilieren, sondern die Klassen in eine
Programm im AVR-Studio einzubinden.
Hier der Beispielcode des Arduino für den TWI-Receiver:
1
// Wire Slave Receiver
2
// by Nicholas Zambetti <http://www.zambetti.com>
3
4
// Demonstrates use of the Wire library
5
// Receives data as an I2C/TWI slave device
6
// Refer to the "Wire Master Writer" example for use with this
7
8
// Created 29 March 2006
9
10
#include <Wire.h>
11
12
void setup()
13
{
14
Wire.begin(4); // join i2c bus with address #4
15
Wire.onReceive(receiveEvent); // register event
16
Serial.begin(9600); // start serial for output
17
}
18
19
void loop()
20
{
21
delay(100);
22
}
23
24
// function that executes whenever data is received from master
25
// this function is registered as an event, see setup()
26
void receiveEvent(int howMany)
27
{
28
while(1 < Wire.available()) // loop through all but the last
29
{
30
char c = Wire.receive(); // receive byte as a character
31
Serial.print(c); // print the character
32
}
33
int x = Wire.receive(); // receive byte as an integer
chris schrieb:
> ist es nur durch die Verwendung des C++ Compilers möglich, einen so> einfach zu verstehenden Code für Leute die nur den Treiber benutzen> wollen zu generieren.
Na ja.
Einfach ist relativ. Wenn man C++ kann, ist das leicht zu verstehen. Man
kann das aber auch alles in C ausdrücken und es bleibt genauso einfach.
Letztenendes geht es nur darum eine Callback-Funktion an einen
Mechanismus zu übergeben und das ist in C und in C++ (leider) völlig
identisch. Wenn man das komplett OOP machen würde (wozu keine
Veranlassung besteht), würde das ein klein wenig anders aussehen :-) Da
würde es dann keinen Receive-Event in Form eines Callbacks geben,
sondern ein Receiver Objekt, welches ein Interface implementieren muss
und dieser Receiver Objekt registriert sich beim Wire Objekt. Aber das
ist eine andere Geschichte.
Tatsache ist aber: So wie der Mechanismus jetzt läuft, gibt es nicht
wirklich einen Grund das alles in C++ zu lassen. Aber wahrscheinlich
gibt es auch keinen wirklichen Grund das alles von C++ auf C
umzuschreiben :-)
Naja, die Call-Back Funktion fällt in diesem Beispiel besonders ins
Auge.
Allerdings finde ich diese Methode auch nicht unbedingt als den gro0en
Vorteil dieser C++ Implementierung.
Der große Vorteil liegt meiner Meinung nach in der "Polymorphie", d.h.
dass ich die Methoden mit gleichem Namen aber unterschiedlichen
Argumenttypen aufrufen kann.
wie z.B.
int x=1
Serial.println(x);
oder
Serial.println("hallo");
Im Anhang habe ich meine Beispielprogramm als AVR-Studio Template
erstellt, d.h. die C++ spezifischen Setups ausgelagert. Das sollte die
Benutzung von C++ im AVR-Studio ziemlich vereinfachen.
Vielleicht könnt Ihr mal draufschauen, ob es so passt.
Genau die ueberladenen Methoden fuehren im Zusammenhang mit
automatischer Typkonvertierung (und am besten noch Defaultparametern) zu
den herrlichsten Fehlern.
Hmm, rein optisch finde ich es aber sehr ansprechend. Vielleicht muss
man die Methoden einfach gut kennen, um diese Fehler zu vermeiden.
So, jetzt bin ich schon wieder auf das Problem gestoßen, mit dem ganz zu
Beginn meiner Annäherung an C++ auf dem AVR gekämpft habe.
Ich nehme die TWI-Klasse des Arduino ( "Wire_I2C.zip" weiter oben
gepostete ) und nehme diese Klasse in mein simples AVR-Studio Projekt
mit auf ( benutze die Klasse aber noch nicht im Programm )
Dann ergibt sich folgende Fehlermeldung:
../Wire.cpp:147: undefined reference to `twi_transmit'
Wire.cpp sollte normalerweise die Funktionen aus twi.c benutzen. Ganz am
Anfang von Wire.cpp steht:
extern "C" {
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "twi.h"
}
Damit sollte doch eigentlich die Funktionen aus twi.h bekannt sein.
Wieso findet der Kompiler die Funktion nicht? Das ist mir ein fölliges
Rätsel.
Ollz schrieb:
>> Jetzt meine Frage: kann ich damit wirklich neue Objekte erzeugen?>> Ja, bis das RAM alle ist. Und das ist schnell alle.
Woher weißt du denn, wie viel RAM bei mir (oder beim OP) frei ist?
>Woher weißt du denn, wie viel RAM bei mir (oder beim OP) frei ist?
Hallo Jörg,
[bösartige ironie]
manche Menschen wissen eben, wieviel Ram bei den anderen frei ist.
Sie wissen auch, was für die anderen gut ist. Ihr Weltbild ist schon
abgeschlossen und deshalb gehen sie davon aus, dass alles was sie für
richtig halten automatisch auch für die anderen gilt. Ich nehme an, bei
Ollz ist schon lange kein Ram mehr frei ;-)
[/bösartige ironie]
Ich habe mal getestet, wieviel Ram beim generieren eins neuen
"CRectangle" Objekts reserviert wird. So wie es mir scheint, sind es nur
die paar Bytes für die lokalen Variablen. Also bei Objekten mit wenig
lokalen Variablen eine recht überschaubare Zahl.
Aber lassen wir das. Vielleicht hast Du als einer der Compilerentwickler
eine Idee:
1
//Wire.cpp sollte normalerweise die Funktionen aus twi.c benutzen. Ganz am
2
//Anfang von Wire.cpp steht:
3
4
extern "C" {
5
#include <stdlib.h>
6
#include <string.h>
7
#include <inttypes.h>
8
#include "twi.h"
9
}
Wenn über das extern "C" ... zusätzlich #include "twi.h" schreibe,
compiliert das Programm fehlerlos und ich kann es im Simulator testen.
Komisch, oder?
chris schrieb:
> Komisch, oder?
Nein, nicht wirklich.
Dieser extern "C" Rundumschlag ist wirkungslos, solange twi.c nicht auch
wirklich im C-Modus compiliert wurde. Mach das nicht!
Mach jedes einzelne Header File auf, welches zu einem reinen C-Modul
gehört und mache dort die extern "C" Deklaration. Und dann sorge auch
dafür, dass twi.c auf jeden Fall neu compiliert wird.
Oder aber. Mach nirgends eine extern "C" definition und gib allen Source
Code Files dieselbe Endung (.cpp oder .C, jenachdem was dein Compiler
haben will um es als C++ Quellcode zu erkennen) oder stell alles so ein,
dass es immer mit dem C++ Compiler übersetzt wird.
Dein Problem: Name mangling
Anscheinend ist twi.c mit Name-Mangling übersetzt worden (also im C++
Mode) und in der Verwendung schaltest du mit dem extern "C" das
Name-mangling beim Verwender aus. Effekt: der Linker sucht nach
tw_transmit, die in Wirklichkeit als QWHJ@tw_transmit@1AGDAJ übersetzt
wurde.
Das alles ist: Pain in the ass
Daher: Alles so einfach wie möglich halten. Wenn du sowieso C++ arbeiten
willst, dann nenn all deine Dateien *.cpp und du sparst dir eine Menge
Probleme.
Hallo Karl heinz,
danke für die Antwort. Die bringt mich jetzt etwas zum grübeln:
Eigentlich wollte ich die Arduino Treiber so verwenden wie sie sind,
d.h. also das twi.c als twi.c lassen. Dann treten aber die von Dir
beschriebenen Probleme auf. Wenn ich die twi.c in twi.cpp umbennene,
muss ich in allen files wie z.B. Wire.cpp die twi.h einbinden wollen den
Code von extern C ... rauswerfen und die H-Files direkt einbinden. Habe
ich das richtig verstanden?
Hmm... das macht die ganze Sache natürlich wieder etwas unangenehmer,
weil ich ja nachschauen muss, wo diese zeilen überall geändert werden
müssen.
Das fühlt sich irgendwie "inhomogen" an.
Gruß,
chris
chris schrieb:
> Sie wissen auch, was für die anderen gut ist. Ihr Weltbild ist schon> abgeschlossen und deshalb gehen sie davon aus, dass alles was sie für> richtig halten automatisch auch für die anderen gilt.
Andersrum gibt es auch Menschen, die sich nichts sagen lassen und jeden
guten Rat in den Wind schieben.
Nochmal im Klartext: Du hast eine Menge Probleme, weil du dich an ein
Projekt wagst, fuer das du zuwenig Erfahrung hast. Fuer
Frameworkprojekte (und das hier sieht wie eins aus) braucht man viel
Erfahrung. Und wenn du anfaengst, Sourcen unterschiedlicher Herkunft zu
mischen, allemal.
Zu deinem aktuellen Problem: Stelle sicher, dass die *.c-Files auch vom
C-Compiler uebersetzt werden, nicht vom C++-Compiler. Dann passt auch
die extern "C"-Deklaration.
Hier
http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html
findest du auch ein paar Tipps, wie man sich das Leben beim C-C++ Mix
leichter machen kann.
Insbesonder das Makro __cplusplus ist oft ein Rettungsanker. Und wenn
das bedeutet, dass du die original Arduino Treiber etwas pimpen musst,
dann sei das so.
Aber das wichtigste ist: C Files mit dem C-Compiler. C++ Files mit dem
C++ Compiler. Das hört sich trivial an, kann aber in ein Nightmare
ausarten. Wenn das aus irgendeinem Grunde nicht trivial geht: Alles
durch den C++ Compiler jagen und hoffen, dass das C sauber genug ist,
dass es in C++ keine Probleme aufwirft.
Ha, gerade auf der FAQ Seite ganz unten gefunden
[quote]
C++ doesn't try to make it impossible for bad programmers to write bad
programs; it enables reasonable developers to create superior software.
[/quote]
Wie wahr, wie wahr.
>Stelle sicher, dass die *.c-Files auch vom>C-Compiler uebersetzt werden, nicht vom C++-Compiler. Dann passt auch>die extern "C"-Deklaration.
Hallo Peter,
vielen Dank für Deine Hilfe. Du hast sicher schon einige Jahre Erfahrung
mit AVR-Studio und C++ und kannst mir sagen, sie ich das mit AVR-Studio
bewerkstellige.
Gruß,
chris
Wenn du C++ machen willst, kannst du meines Wissens AVR Studio
komplett vergessen. Kann sein, dass du eine entsprechende C++-
Quelle noch editieren kannst, aber schon beim Generieren des
Makefiles dürfte er aufgeschmissen sein, und C++ Debuggen geht
rein gar nicht (d. h. es geht nur insoweit, wie du C++-Features
benutzt, die es auch in C gibt).
chris schrieb:
> vielen Dank für Deine Hilfe. Du hast sicher schon einige Jahre Erfahrung> mit AVR-Studio und C++ und kannst mir sagen, sie ich das mit AVR-Studio> bewerkstellige.
Ich benutze kein AVR-Studio. Ich entwickle meine Sachen auf MacOS X und
benutze das "uebliche" Makefile mit ein paar kleinen Aenderungen. Aber
frag mich nicht, wo ich letzteres her hatte.
Hallo Jörg,
>Wenn du C++ machen willst, kannst du meines Wissens AVR Studio>komplett vergessen.
Der ganz oben gepostete Link
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=59453
beschreibt ja gerade, wie es mit AVR-Studio geht.
>Kann sein, dass du eine entsprechende C++->Quelle noch editieren kannst, aber schon beim Generieren des>Makefiles dürfte er aufgeschmissen sein, und C++ Debuggen geht>rein gar nicht
Hmm, über das Make-File von AVR-Studio weiss ich nichts. Ich konnte mein
oben gepostetes Beispiel übersetzten und mit kleinen Abweichern auch
debuggen.
Ich liebe die Debug-Funktion von AVR-Studio. Gerade bei Mikrocontrollern
mit ihrer kompliziert zu programmierenden Peripherie ist es wichtig,
dass man deren Registerinhalte und Funktion überprüfen kann. Mir hat das
jedenfalls schon einige Stunden Fehlersuche erspaart.
Zum Problem vom Mischen von C++ und C in AVR-Studio findet sich im Link
folgendes:
"First thing setup. I use AVR Studio, which is almost working with c++.
Almost, because you must manually in project->Configuration
options->Custom options set instead of avr-gcc avr-c++.exe to compile
any of your c++ files."
d.h. der Compiler avr-gcc wird durch avr-c++ ersetzt. Ich nehme mal an,
dass das der Grund ist, warum sich C und C++ im AVR-Studio nicht
vernünftig mischen lässt.
Deshalb gleich meine Frage: Kann der avr-c++ nicht auch standard C
übersetzten? Warum sollte man überhaupt ein reines C-File verwenden?
chris schrieb:
> beschreibt ja gerade, wie es mit AVR-Studio geht.
OK, mir ist nur in Erinnerung, dass zumindest der Debugger mit
den obskureren C++-Features nicht klar kommt.
> d.h. der Compiler avr-gcc wird durch avr-c++ ersetzt. Ich nehme mal an,> dass das der Grund ist, warum sich C und C++ im AVR-Studio nicht> vernünftig mischen lässt.
Ja. Normalerweise macht man die Unterscheidung über die
Dateiendung. Suffix .c heißt, dass der C-Compiler zu benutzen
ist, Suffix .C oder .c++ oder .cpp (und noch ein paar andere)
wählen den C++-Compiler aus. Das Kommando muss dabei aber in
jedem Falle avr-gcc heißen; wenn man explizit mit avr-c++ compiliert,
werden alle Quellen als C++ bewertet.
> Deshalb gleich meine Frage: Kann der avr-c++ nicht auch standard C> übersetzten?
Ein ordentlich geschriebenes C-Programm sollte in der Regel auch durch
einen C++-Compiler laufen können.
Allerdings gibt es subtile Unterschiede. Der offensichtlichste ist,
wenn in C++ reservierte Wörter als Bezeichner verwendet werden:
1
charnew;
2
3
voiddelete(constchar*filename);
Es gibt aber vieles mehr, bspw. ist die Typprüfung in C++ sehr viel
strenger als in C. In C lassen sich void* implizit in andere
Objektzeiger überführen (und das gilt auch nicht als schlechter
Programmierstil), in C++ nicht.
Ganz haarig wird's, wenn das C-Programm benannte Elemente von Arrays
oder Strukturen initialisiert. Dieses Feature ist in C99 in den
Standard aufgenommen worden, stammt aber im Gegensatz zu vielen
anderen C99-Erweiterungen nicht von C++ ab und existiert dort nicht.
Zu guter Letzt gibt's sicher auch genügend hinreichen schlampig
geschriebene C-Programme, die kein C++-Compiler je fressen würde.
> Warum sollte man überhaupt ein reines C-File verwenden?
Zum Beispiel, weil man es irgendwo extern her bekommen hat und
keine Lust hat, es C++-fähig zu machen.
chris schrieb:> Gibt es eigentlich keine Funktion in der libc, um die verfügbare> Speichergröße für "malloc" zu bestimmen?
Sagen wir mal so:
Wenn Du sehr wenig RAM hast, wirst Du kein malloc() benutzen wollen.
Wenn Du ordentlich RAM hast, kannst Du Dir auch ein Betriebssystem
leisten ;-)
chris schrieb:>>Sagen wir mal so:>> [ ] Du hast die Frage beantwortet.> [x] Du wolltest zeigen, dass Du auch da bist.
[ ] Du willst Hilfe
[X] Du willst keine Hilfe
Die Speicherverbrauchsanzeige bleibt allerdings stecken. Sie scheint das
dynamische Allokieren mit "new" nicht zu zählen.
Jetzt muss ich wohl doch die Links durcharbeiten ....
Es sei denn, jemand weis die Lösung auf die Schnelle ...
chris schrieb:> Die Speicherverbrauchsanzeige bleibt allerdings stecken. Sie scheint das> dynamische Allokieren mit "new" nicht zu zählen.> Jetzt muss ich wohl doch die Links durcharbeiten ....> Es sei denn, jemand weis die Lösung auf die Schnelle ...
Die simpelste Funktion zum Ermitteln des freien RAM-Speichers, die von
Arduino-Programmierern gerne verwendet wird, ist diese:
Allerdings ermittelt diese Funktion bei einem fragmentierten
RAM-Speicher nicht den gesamten freien Speicher, sondern nur den (im
Regelfall) größten freien Speicherbereich auf dem Heap, den man mit
malloc() reservieren könnte. Freie RAM-Fragmente, die nach der
Reservierung wieder zwischen anderen reservierten Speicherbereichen
freigegeben wurden, bleiben bei dieser Funktion außen vor.
chris schrieb:> Die Rechtecke brauche wohl 10 Byte und die Quadrate 8 Byte.
Das hättest du mit einem sizeof auch billiger ermitteln können.
Die Angabe des freien Speichers ist eine zweischneidige Sache. Denn auch
wenn du weißt, das noch 100 Bytes frei sind, kann es durchaus sein, dass
eine Speicheranforderung über 40 Bytes nicht erfüllt werden kann. Denn
100 Bytes frei bedeutet nicht notwendigerweise, dass die 100 Bytes in
einem zusammenängenden Stück frei sind. 20 Speicherlöcher mit jeweils 5
Bytes sind in Summe auch 100 Bytes. Aber keines der freien Löcher ist
eben groß genug, um darin eine 40 Byte Anforderung unterzubringen.
Jürgen S. schreibt zur obigen Funktion:
"Allerdings ermittelt diese Funktion bei einem fragmentierten
RAM-Speicher nicht den gesamten freien Speicher, sondern nur den (im
Regelfall) größten freien Speicherbereich auf dem Heap, den man mit
malloc() reservieren könnte."
Soweit ich es verstehe, liefert die Funktion garantiert freien Bereich.
Karl Heinz schrieb:> Die Angabe des freien Speichers ist eine zweischneidige Sache. Denn auch> wenn du weißt, das noch 100 Bytes frei sind, kann es durchaus sein, dass> eine Speicheranforderung über 40 Bytes nicht erfüllt werden kann. Denn> 100 Bytes frei bedeutet nicht notwendigerweise, dass die 100 Bytes in> einem zusammenängenden Stück frei sind.
Die von mir vorhin gepostete simple Funktion zählt allerdings keine
freien RAM-Fragmente zusammen, sondern liefert als einzige Angabe die
Größe des freien Speicherblocks zwischen Ende des Heaps und Anfang des
Stacks. Und dieser RAM-Block ist immer am Stück frei.
Hier im Bild zu sehen der kleinere der freien grauen Blöcke zwischen
__brkval und SP:
http://www.nongnu.org/avr-libc/user-manual/malloc.html
Vollständig reservieren sollte man aber auch diesen Bereich natürlich
trotzdem nicht, weil dann keine Funktionen mehr laufen, wenn der Stack
mit seinen Adressen nicht mehr nach unten wachsen kann, sobald eine
Funktion aufgerufen wird.
Je nach Schachtelungstiefe von Funktionen muß da immer etwas RAM frei
bleiben, das man nicht reservieren darf.