2009-04-19 6 views
6

Ich arbeite an einem Gruppen-Senior-Projekt für meine Universität und ich bin in eine große Hürde gerannt, als ich versuchte, meinen Code zum Laufen zu bringen.Wie können Sie C++ ausführen, wenn Ihr eingebetteter Compiler keine Operator-Neu- oder STL-Unterstützung hat?

Der Compiler, den wir für unseren 8-Bit-Atmel-Mikrocontroller haben, unterstützt die neuen oder löschenden Operatoren nicht, und er unterstützt die C++ - STL nicht. Ich könnte es in C programmieren, aber ich muss einen A * Algorithmus implementieren, den ich vorher noch nie gemacht habe. Während ich C anfangs ausprobiert habe, habe ich schnell gemerkt, dass ich noch nie reines C gemacht habe. Der Versuch, Objekte mit Strukturen und Funktionen zu modellieren, verlangsamt mich, da ich mich an die viel sauberere C++ - Syntax gewöhnt habe.

Egal, die genaue Wortlaut für meine Compiler Mängel ist hier zu finden: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_cplusplus

sie zu überwinden und immer noch verwenden C++ Ich habe die folgenden Möglichkeiten in Betracht gezogen. 1) Ordnen Sie nichts zu, verwenden Sie einfach Vorlagen, um feste Arrays auf dem Stack zu generieren. 2) Zuordnen und finden Sie einen Hack, um den Konstruktor für Objekte aufzurufen, sobald ich den Platz für sie zugewiesen habe. Placement new ist keine Option, da new kein Operator ist. 3) Verwenden Sie einfach C und saugen Sie es auf, es ist ein Mikrocontroller, warum werde ich Lust bekommen? 4) Finden Sie einen besseren Compiler, der wahrscheinlich $$$ kosten wird.

Die zweite Option ist die härteste, aber es würde am meisten auszahlen, wie ich diesen Code schreiben kann. Ich stelle mir jedoch vor, dass das Debuggen ein großer Schmerz sein könnte, wenn ich falsch liege. Ich denke darüber nach, Objekte auf dem Stapel zu erstellen, ihre Bits in den zugewiesenen Bereich zu kopieren und dann die Bits im Objekt auf Null zu setzen, so dass es seinen Destruktor nicht aufruft. Dazu würde ich direkt auf die Bits mit einem vorzeichenlosen char-Zeiger und dem sizeof-Operator zugreifen, um die Bytezahl zu erhalten.

Das klingt schrecklich und ich weiß nicht, ob es zuverlässig funktionieren könnte, aber ich denke darüber nach. Ich weiß, dass VTables ein Problem sein können, aber ich habe nicht vor, irgendwelche VTables zu haben, da es nur ein 8-Bit-Mikrocontroller ist.

+3

Wenn ich richtig die 8bit Atmels erinnern nicht genug RAM sogar darüber nachdenken, dynamischen Speicher a'la malloc/neu. – lothar

+0

Wenn ich diese Operatoren überlasten kann, kann ich kontrollieren, woher der Speicher kommt. Es kann auf dem Heap oder dem Stapel sein. –

+0

Ich fand heraus, dass ich nur 1KB zum Spielen hatte, was im Wesentlichen A * als einen brauchbaren Pfadfinder für meinen Roboter ausschloss. Ich benutze jetzt ein einfaches DFS, bis ich eines mit mehr RAM bekommen kann. –

Antwort

10

Nur zur Erinnerung, das Nullsetzen der Bits in einem Objekt hat keinen Einfluss darauf, ob der Destruktor aufgerufen wird (es sei denn, der Compiler hat eine besondere Eigenart, die dieses Verhalten ermöglicht). Schreiben Sie einige Protokollanweisungen in Ihren Destruktor, um dies zu testen.

Die Strukturierung Ihres Programms, um nichts zuzuordnen, ist wahrscheinlich die Art, wie das System entworfen wurde. Ich habe noch nicht mit eingebetteten Systemen gearbeitet, jedoch habe ich einige erfahrene Embedded-Shops gelesen, die die Verwendung von dynamischem Speicher abhalten, da die Laufzeitumgebung nur geringe Mengen davon enthält.


Wenn Sie jedoch müssen, können Sie immer noch neue Placement verwenden. Wenn Sie die <new> Header nicht haben, hier sind die entsprechenden Zeilen direkt von ihm auf meiner Version von GCC:

// Default placement versions of operator new. 
inline void* operator new(std::size_t, void* __p) throw() { return __p; } 
inline void* operator new[](std::size_t, void* __p) throw() { return __p; } 

// Default placement versions of operator delete. 
inline void operator delete (void*, void*) throw() { } 
inline void operator delete[](void*, void*) throw() { } 

-Stick, der irgendwo in einer Header-Datei von jeder Quelldatei enthalten, die Platzierung neue/Löschen verwendet.

Beispieldatei, die diese Tests:

#include <cstdio> 
#include <new> 

int 
main(int argc, char** argv) 
{ 
    typedef char const* cstr; 
    char foobar[16]; 
    cstr* str = new (&foobar) cstr(argc > 1 ? argv[1] : "Hello, world!"); 
    std::puts(*str); 
    str->~cstr(); 
} 

Auf meiner Version von GCC, das überhaupt nicht verwenden libstdc++ (wenn -fno-exceptions verwendet wird).


Nun, wenn Sie, dass mit malloc (wenn Ihre Plattform bietet diese) kombinieren möchten, dann können Sie dies tun:

#include <cstdio> 
#include <cstdlib> 

inline void* operator new (std::size_t n) {return std::malloc(n);} 
inline void* operator new[](std::size_t n) {return std::malloc(n);} 
inline void operator delete (void* p) {std::free(p);} 
inline void operator delete[](void* p) {std::free(p);} 

int 
main(int argc, char** argv) 
{ 
    typedef char const* cstr; 
    cstr* str = new cstr(argc > 1 ? argv[1] : "Hello, world!"); 
    std::puts(*str); 
    delete str; 
} 

Dies Sie die Standard-new/delete verwenden, die Sie vertraut sind, ohne die Verwendung von libstdc++ zu erfordern.

Viel Glück!

+0

Wow, es sieht so aus. Ich konnte die von Ihnen gelieferten Header nicht verwenden, aber diese Operatorfunktionen scheinen zu funktionieren. Nach dem, was ich über diese Operatoren gelesen habe, müssen new [] und delete [] die Array-Größe kennen, um alle benötigten Destruktoren aufzurufen, aber in meinem Test wurden alle benötigten Destruktoren aufgerufen. –

0

Warum schreiben Sie nicht zuerst auf Ihrem Desktop-Computer, unter Berücksichtigung der Einschränkungen des Compilers, debuggen Sie es, stellen Sie sicher, dass es perfekt funktioniert und dann nur in die eingebettete Umgebung verschieben?

+0

Ich denke nicht, dass dies eine sehr gute Idee ist, da es so viele Einschränkungen und Unterschiede auf Embedded-Plattformen gibt (wenig RAM, 8 Bit statt 32 Bit), so dass Sie es nie so schaffen werden. – mmmmmmmm

23

Bekämpfen Sie nicht Ihre Werkzeuge. Wenn der einzige Compiler, den Sie für Ihr eingebettetes System haben, ein C-Compiler ist, lernen Sie C - es ist nicht schwierig. Der Versuch, eine Bastard-Version der beiden Sprachen zu produzieren, nur um ein ziemlich einfaches Programmierproblem zu lösen, wird nur in Tränen enden. Wenn Ihre eingebettete Plattform nicht einmal einen C-Compiler, sondern nur einen Assembler unterstützt, wäre Ihr erster Impuls, sich hinzusetzen und einen C++ - Compiler in Assembler zu schreiben? Ich hoffe nicht, ich hoffe, Sie würden stattdessen setzen und lernen, den Assembler zu verwenden, um Ihre Aufgabe abzuschließen - Schreiben eines C++ - Compilers (oder sogar ein C-Compiler) wäre völlig unangemessene Verwendung Ihrer Zeit und würde fast mit Sicherheit zum Scheitern führen .

+0

Obwohl ich zustimme, dass die Bekämpfung von Werkzeugen dumm ist, lohnt sich die Investition in die Werkzeugentwicklung normalerweise. Wenn und wenn ich Assembler-Schreiben verwendet habe, war ein kleines Preprozessor-basiertes DSL es wert. Allerdings würde ich nie so weit gehen, einen C++ - Compiler zu schreiben :) –

+0

Oh, ich stimme zu, und zurück, als ich Assembler schrieb (vor langer Zeit), benutzte ich Makro-Bibliotheken, die ich geschrieben hatte. Und ich habe tatsächlich einen halben C-Compiler im Z80-Assembler geschrieben (um ein bestimmtes Problem nicht zu lösen), bevor ich zu meinen Sinnen kam, habe es in C neu geschrieben und es kompiliert. Es funktioniert immer noch nicht, aber es war viel weniger Arbeit :-) –

+0

Und vergessen Sie nicht, dass die meisten eingebetteten C-Compiler nur mit einem sehr begrenzten Untersatz der C-Bibliothek kommen. Einer der wichtigen Teile, die meiner Meinung nach fehlen, ist die Heap-Verwaltung (malloc/free). – mmmmmmmm

4

Nur weil es diese Tools nicht hat, heißt das nicht, dass Sie nicht von C++ profitieren können. Wenn das Projekt groß genug ist, könnte der Zugriff auf objektorientiertes Design allein Motivation genug sein.

Wenn es "neu" nicht unterstützt, liegt es vermutlich daran, dass es nicht sinnvoll ist, automatisch zwischen einem Heap und dem Stack zu unterscheiden. Dies liegt möglicherweise an Ihrer Speicherkonfiguration. Es kann auch sein, dass Speicherressourcen so eingeschränkt sind, dass nur sehr sorgfältige Zuweisung sinnvoll ist. Wenn Sie unbedingt Ihren eigenen "neuen" Operator implementieren müssen, können Sie sich mit der Anpassung von Doug Lea's malloc befassen. Ich glaube, er hat seinen Allokator unter ähnlichen Umständen begonnen (C++ ist neu implementiert worden).

Ich liebe die STL, aber es ist immer noch möglich, nützliche Dinge ohne sie zu tun. Abhängig vom Umfang des Projekts ist es möglicherweise besser, ein Array zu verwenden.

2

Ich hatte einen ähnlichen Compiler, der eine bizarre Version der Embedded-C++ standard implementiert. Wir hatten operator new, die Konstruktoren für uns aufrufen würden und Destruktoren in den meisten Fällen genannt wurden. Der Compiler/Runtime-Hersteller ging und implementierte try und catch mit setjmp und longjmp als Bequemlichkeit für den Ingenieur. Das Problem war, dass sie nie erwähnt haben, dass ein throw keine Destruktoren von lokalen Objekten verursachen würde!

Wie auch immer, unsere Gruppe erbte die Codebasis, nachdem jemand eine Anwendung geschrieben hatte, die wie Standard C++ funktionierte: mit RAII-Techniken und all den anderen Vorteilen. Wir haben es am Ende in einer Reihe von uns umgeschrieben objektorientierten C statt. Vielleicht möchten Sie in Erwägung ziehen, nur das Bullet zu beißen und in C zu schreiben. Anstelle von Konstruktoren haben Sie eine explizit aufgerufene Initialisierungsmethode. Destruktoren werden zu einer explizit als Terminierungsmethode bezeichneten Methode. Es gibt nicht viel von C++, das Sie in C ziemlich schnell nachahmen können. Ja, MI ist ein Schmerz in der ... aber Einzelvererbung ist ziemlich einfach. Werfen Sie einen Blick auf this PDF für einige Ideen. Es beschreibt fast den Ansatz, den wir gemacht haben. Ich wünschte wirklich, ich hätte unsere Methode irgendwo niedergeschrieben ...

0

Bei eingebetteter Arbeit konnte ich die C-Laufzeit nicht einmal für Speicherbeschränkungen verknüpfen, aber die Hardware hatte eine DMA-Anweisung (dynamic memory allocator), also schrieb ich meine eigene malloc mit dieser Hardware, wahrscheinlich hat Ihre Hardware eine ähnliches Feature, so dass Sie ein malloc und dann ein neues auf dem malloc basierend schreiben könnten.

Irgendwie am Ende habe ich 99% Stapelzuweisungen verwendet, und ein paar Grenzen setzt os statische Objekte, die ich recyceln würde, indem Sie an Ort und Stelle. Das könnte mir eine gute Lösung sein.

6

Ich denke, Sie nähern sich dem Problem aus einem Gesichtspunkt, der nicht optimal ist.

Sie konzentrieren sich auf den Compiler (oder dessen Fehlen), anstatt sich auf die HARDWARE zu konzentrieren.

Die wahrscheinlichste Antwort auf Ihre Hauptfragen ist, "weil die Hardware nicht alles C++ Zeug unterstützt". Eingebettete Hardware (Microcontroller) sind für die Anpassung des Hardware-Designs - Speicherkarten, Interrupt-Handler, I/O, etc. notiert.

Meiner Meinung nach sollten Sie FIRST einige Zeit mit dem Hardware-Buch für den Mikrocontroller verbringen, lernen die Ein- und Ausgänge des Geräts - dh wie es entworfen wurde und für welchen Hauptzweck. Einige wurden für schnelle Speichermanipulation entwickelt, einige für schnelles E/A-Handling, einige für A/D-Arbeit, einige für Signalverarbeitung. Der Typ des Mikrocontrollers schreibt die Assembler-Anweisungen vor, die sie dafür geschrieben haben, und dies bestimmt, was ein höherer Compiler effizient tun kann.

Wenn das wichtig ist, sollten Sie sich auch etwas Zeit nehmen, um sich den Assembler anzuschauen - er wird Ihnen sagen, was die Designer für wichtig hielten. Es wird Ihnen auch viel darüber erzählen, wie viel Sie von einem High-Level-Compiler bekommen können.

Im Allgemeinen unterstützen Mikrocontroller C++ nicht, da das Design sich nicht um Objekte oder die komplexe Speicherbehandlung kümmert (aus C++ Perspektive). Es kann getan werden, aber Sie versuchen oft, einen runden Stift in einem quadratischen Loch zu schlagen, um Konstruktoren und Destruktoren (und "neu" und "löschen") in der Mikroumgebung zu erhalten.

Wenn Sie einen C-Compiler für diese Einheit haben, betrachten Sie es als Segen. Ein guter C-Compiler ist oft "mehr als genug", um exzellente eingebettete Software zu erstellen.

Cheers,

-Richard

1

Sie einige hilfreiche Code auf meinem A* tutorial website finden. Obwohl die code ich geschrieben habe, um dies zu unterstützen verwendet STL in sollte leicht sein, die STL-Unterstützung zu strippen. Außerdem gibt es einen Pool-Allokator (fsa.h), den ich geschrieben habe, um STL auf Spielekonsolen zu beschleunigen. Es ist C++ - Code, aber ich habe es ursprünglich von C portiert und ich denke nicht, dass es schwer wäre, das anders zu machen. Der Code wird von über 10.000 Menschen getestet, also ist es eine gute Basis, um von dort zu starten.

Das Ersetzen der STL-Strukturen, die ich verwende, ist kein Problem, da es auf Vektoren beschränkt ist. Ich verwende einen der Vektoren als eine Prioritätswarteschlange, die die Heap-Funktionen (make_heap und push_heap) verwendet. Sie können das durch meine old C code ersetzen, die eine in C implementierte Prioritätswarteschlange hat, die nur in Ihren Code fallen sollte. (Was nur eine allokiert, so können Sie das durch einen Zeiger auf einen reservierten Bereich des Speichers ersetzen.

Wie Sie in diesem Codefragment aus der Kopfzeile sehen können, ist der Hauptunterschied in C-Code, dass es keine gibt Zeiger, kein Objekt, also nimmt Ihr Code normalerweise einen Objektzeiger als erstes Argument.

void PQueueInitialise(PQUEUE *pq, int32 MaxElements, uint32 MaxRating, bool32 bIsAscending); 
void PQueueFree(PQUEUE *pq); 
int8 PQueuePush(PQUEUE *pq, void *item, uint32 (*PGetRating) (void *)); 
int32 PQueueIsFull(PQUEUE *pq); 
int32 PQueueIsEmpty(PQUEUE *pq); 
void *PQueuePop(PQUEUE *pq, uint32 (*PGetRating) (void *)); 
+0

Ihr "alter C-Code" ruft malloc() auf. Ich glaube nicht, dass es so viele C-Compiler für 8-Bit-Embedded-Plattformen gibt, die genug von der C-Bibliothek unterstützen, um Ihnen die Heap-Verwaltung zu ermöglichen (malloc/free)! – mmmmmmmm

+0

Wenn Sie es genauer betrachten, ruft es malloc auf, um einen Speicherblock für eine Prioritätswarteschlange mit fester Größe zuzuweisen. Die Warteschlange selbst verwendet diesen einzelnen Block dann sehr effizient. So kann es konvertiert werden, um malloc mit einer Codezeile nicht zu verwenden. – justinhj

+0

Wer hat das gewählt? Das ist nützliches Zeug, ich brauche A * Hilfe auch. Ich habe Google-Code und ein Buch über AI, aber ich denke immer noch darüber nach. –