2017-12-19 4 views
8

Ich hatte vor einigen Jahren ein Bare-Metal-Projekt (Cortex-M) gestartet. Bei der Projekteinrichtung haben wir uns entschieden, gcc toolchain mit C++ 11/C++ 14 usw. zu verwenden und sogar C++ - Ausnahmen und rtti zu verwenden.Wie vermeidet man C++ Code Bloat, der von Template Instanziierung und Symboltabelle ausgegeben wird?

Wir verwenden derzeit gcc 4.9 from launchpad.net/gcc-arm-embedded (mit einem Problem, das uns derzeit daran hindern, auf eine neuere GCC-Version zu aktualisieren).

Zum Beispiel würde ich schreibe eine Basisklasse und eine abgeleitete Klasse wie folgt (siehe auch laufendes Beispiel here):

class OutStream { 
public: 
    explicit OutStream() {} 
    virtual ~OutStream() {} 
    OutStream& operator << (const char* s) { 
     write(s, strlen(s)); 
     return *this; 
    } 
    virtual void write(const void* buffer, size_t size) = 0;  
}; 

class FixedMemoryStream: public OutStream { 
public: 
    explicit FixedMemoryStream(void* memBuffer, size_t memBufferSize): memBuffer(memBuffer), memBufferSize(memBufferSize) {} 
    virtual ~FixedMemoryStream()  {} 
    const void* getBuffer() const  { return memBuffer; } 
    size_t  getBufferSize() const { return memBufferSize; } 
    const char* getText() const  { return reinterpret_cast<const char*>(memBuffer); } ///< returns content as zero terminated C-string  
    size_t  getSize() const  { return index; }          ///< number of bytes really written to the buffer (max = buffersize-1) 
    bool   isOverflow() const { return overflow; } 
    virtual void write(const void* buffer, size_t size) override { /* ... */ } 
private: 
    void* memBuffer = nullptr; ///< buffer 
    size_t memBufferSize = 0;  ///< buffer size 
    size_t index = 0;    ///< current write index 
    bool overflow = false;  ///< flag if we are overflown 
}; 

Damit die Kunden meiner Klasse sind nun in der Lage zB zu verwenden:

char buffer[10]; 
FixedMemoryStream ms1(buffer, sizeof(buffer)); 
ms1 << "Hello World"; 

Jetzt möchte ich würde die Verwendung der Klasse etwas bequemer machen und stellte die folgende Vorlage:

template<size_t bufferSize> class FixedMemoryStreamWithBuffer: public FixedMemoryStream { 
public: 
    explicit FixedMemoryStreamWithBuffer(): FixedMemoryStream(buffer, bufferSize) {} 
private: 
    uint8_t buffer[bufferSize]; 
}; 

Und ab jetzt können meine Kunden schreiben:

FixedMemoryStreamWithBuffer<10> ms2; 
ms2 << "Hello World"; 

Aber von jetzt hatte ich zunehmende Größe meiner ausführbaren Binärdatei beobachtet. Es scheint, dass gcc Symbolinformationen für jede andere Template-Instanziierung von FixedMemoryStreamWithBuffer hinzugefügt hat (weil wir rtti aus irgendeinem Grund verwenden).

Könnte es eine Möglichkeit geben, Symbolinformationen nur für bestimmte Klassen/Vorlagen/Template-Instanziierungen zu entfernen?

Es ist in Ordnung, eine nicht portable gcc einzige Lösung dafür zu bekommen.

Aus irgendeinem Grund haben wir uns entschieden, Vorlagen anstelle von Präprozessor-Makros zu bevorzugen. Ich möchte eine Präprozessor-Lösung vermeiden.

+0

Was ist, wenn Sie entfernen die virtuellen Funktionen? Nur polymorphe Objekte können RTTI verwenden. – Quentin

+0

Die virtuellen Funktionen werden definitiv benötigt, weil ich einige andere Klassen habe, die von 'OutStream' abgeleitet sind (z.B. UartStream, TcpStream und so weiter). – Joe

+0

Es besteht nur ein Bedarf, wenn Sie diese abgeleiteten Klassen polymorph (d. H. Durch Ausdrücke des Typs der Basisklasse) verwenden. – Quentin

Antwort

2

Ja, es gibt eine Möglichkeit, die erforderlichen Symbole fast auf 0 zu bringen: Verwenden der Standardbibliothek. Ihre OutStream Klasse ist eine vereinfachte Version von std::basic_ostream. Ihr OutStream::write ist wirklich nur std::basic_ostream::write und so weiter. Werfen Sie einen Blick darauf here. Überlauf wird sehr genau gehandhabt, obwohl der Vollständigkeit halber auch underflow behandelt wird, d. H. Die Notwendigkeit des Datenabrufs; Sie können es als undefined lassen (es ist virtual auch).

In ähnlicher Weise ist Ihr FixedMemoryStreamstd::basic_streambuf<T> mit einer festen Größe (a std::array<T>) Get/Put-Bereich.

Also, machen Sie einfach Ihre Klassen von den Standard-Erben und Sie werden auf Binärgröße abgeschnitten, da Sie bereits deklarierte Symbole wiederverwenden.


Jetzt in Bezug auf template<size_t bufferSize> class FixedMemoryStreamWithBuffer. Diese Klasse ist sehr ähnlich wie std::array<std::uint8_t, bufferSize> für die Art und Weise wie Speicher angegeben und erworben wird. Sie kann nicht viel darüber optimieren: jede Instanziierung ist eine verschiedene Art mit allem, was das bedeutet. Der Compiler kann nicht "verschmelzen" oder irgendetwas Magisches an ihnen vornehmen: Jede Instanz muss ihren eigenen Typ haben. Also entweder auf std::vector oder haben einige feste Größe spezialisierte Stücke, wie 32, 128 usw. und für irgendwelche Werte dazwischen würde die richtige wählen; Dies kann vollständig zur Kompilierungszeit erreicht werden, also keine Laufzeitkosten.

+0

Das würde Code aus dem OP-Programm durch Code aus der Standardbibliothek ersetzen –

+0

Meine OutStream und abgeleitete Klasse & Vorlage ist nur ein Beispiel zur Beschreibung des Problems. Ich habe einige andere Fälle, in denen Klassen keine ähnlichen in der Standardbibliothek haben. Für den 'OutStream'-Fall muss ich jedoch 'stdio' nicht verwenden, da es meine Codesize für eine ziemlich große Menge an Bytes hochlädt und auch etwas Speicher von Heap zuweist, was auf Bare-Metal-Systemen meist teuer ist. – Joe

+0

@Joe: ob Ihr Code "Iostream" verwendet (oder verwenden kann), der zweite Teil meiner Antwort gilt noch. BTW, du könntest versuchen RTTI aus der Gleichung zu nehmen und so "-nortti" einzuschalten. RTTI und Ausnahmen sind schwer auf Symbole und Speicher. – edmz

2

Denken Sie daran, dass der Compiler für jede Instanz des FixedMemoryStreamWithBuffer <> sowie für jede Klasse in der Vererbungskette auch separate v-table- (sowie RTTI-Informationen) generiert.

Um das Problem zu lösen ich mit einiger Konvertierungsfunktion und/oder Betreiber innen mit Haltung statt Vererbung empfehlen würde:

template<size_t bufferSize> 
    class FixedMemoryStreamWithBuffer 
    { 
     uint8_t buffer[bufferSize]; 
     FixedMemoryStream m_stream; 
    public: 
     explicit FixedMemoryStreamWithBuffer() : m_stream(m_buffer, bufferSize) {} 
     operator FixedMemoryStream&() { return m_stream; } 
     FixedMemoryStream& toStream() { return m_stream; } 
    }; 
+0

Interessanter Ansatz. Da 'FixedMemoryStreamWithBuffer' nicht länger polymorph ist, hat der Compiler eine echte Chance, die Symbolinformationen loszuwerden. Aber leider muss ich die 'toStream()' Methode wie 'ms.toStream() <<" Hallo Welt "; ab jetzt aufrufen, weil der Darsteller für diesen Fall nicht implizit arbeitet. – Joe

+0

@Joe, aber was hindert Sie daran, Ihren eigenen "Operator <<" zu definieren, der die Anfrage an den richtigen Stream weiterleitet? In der Tat, ich nehme an, Sie müssen das gesamte Stream-Interface (Funktionen wie flush()) neu definieren, damit es sich wie ein echter Stream verhält. Ja, es kann eine Menge zusätzlicher Arbeit sein, aber es löst Ihr Problem unnötiger RTTI- und v-Tabellen-Einführungen. Wie die Klassiker sagen: "Es gibt keinen einfachen Ausweg!" –

+0

Ja, ich muss eine Menge 'operator <<' für alle Datentypen kopieren, die von meinem 'OutStream' für jetzt und für die Zukunft unterstützt werden. Dies verstößt definitiv gegen [Dry] (https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). – Joe

Verwandte Themen