Anfängerfrage: mit new nicht auf den heap



  • Hallo zusammen,

    ich programmiere auf einem stark ressourcenlimitierten embedded system. Da der Zugriff auf den externen Speicher länger dauert, habe ich es bisher vermieden Objekte dynamisch anzulegen, damit alles auf dem internen Speicher landet.
    Der Code wird nun jedoch arg unübersichtlich, wenn mir weiterhin verwehrt bleibt Objekte dynamisch zur Laufzeit zu generieren und ihnen dann mitzuteilen von welchem Typ sie sind.

    Gibt es die Möglichkeit ein Objekt bspw. mit einem strategy pattern ohne new zu erzeugen, so dass es auf dem Stack landet?

    Viele Grüße
    Zisko



  • Zisko schrieb:

    Objekte dynamisch zur Laufzeit zu generieren und ihnen dann mitzuteilen von welchem Typ sie sind.

    Brauchst du das wirklich? Weil wenn ja, dann führt am Heap nichts vorbei.

    In meiner Erfahrung braucht man das jedoch nicht wirklich. Es gibt eine Anzahl Klassen und von jeder Klasse will man max. 1 Instanz. Dann lässt sich das über statische Variablen lösen:

    // Header
    class Abstract { virtual void f(); };
    Abstract *get_it(int id);
    
    // cpp
    namespace { // schützt vor Namenskollisionen, nicht wirklich nötig
    
    class StratA : Abstract { ... };
    class StratB : Abstract { ... };
    
    StratA strat_a_impl;
    StratB strat_b_impl;
    
    }
    
    Abstract *get_it(int id) { return id ? &strat_a_impl : &strat_b_impl; }
    


  • Viele Compiler bieten alloca() .
    Ist aber mit Vorsicht zu genießen.



  • Caligulaminus schrieb:

    Viele Compiler bieten alloca() .

    Ja, alloca eignet sich perfekt um etwas von einer Funktion zurückzugeben 🙄



  • Grundlegend habe ich ein Klasse A in der sich eine Klasse B befindet. Von Klasse B gibt es zwei Varianten, die sich unterscheiden. Beim erzeugen von A möchte ich dem Konstruktor eine Variable übergeben, die dann die richtige Variante von B erzeugt.

    Derzeit löse ich das so, dass ich für A unterschiedliche namespaces verwende und je nach namespace wird dann ein A mit Variante 1 von B erzeugt oder A mit Variante 2.

    Es funktioniert, aber ist nicht grade die eleganteste Lösung.



  • allola schrieb:

    eignet sich perfekt um etwas von einer Funktion zurückzugeben 🙄

    Habe ich nie behauptet.
    In der Hinsicht besteht kein Unterschied zu automatischen Objekten.



  • ressourcenlimitierten embedded system

    Normalerweise schoepft man auf solchen Systemen nicht alle Features von C++ aus. Und wenn ich Strategiepattern im Zusammenhang mit ressourcenlimitierten Systemen hoere, dann schrillen schon mal alle Alarmglocken bei mir.



  • Zisko schrieb:

    Derzeit löse ich das so, dass ich für A unterschiedliche namespaces verwende und je nach namespace wird dann ein A mit Variante 1 von B erzeugt oder A mit Variante 2.

    Das kannst du auch billiger mit nem Template bekommen.
    Ist aber immer noch net wirklich elegant, weil's dann immer noch zwei verschiedene A sind.

    Im Prinzip brauchst du bloss Speicher reservieren der gross genug für das grössere der beiden B ist, und passend aligned für beide.
    In C++11 geht das mit aligned_storage
    http://en.cppreference.com/w/cpp/types/aligned_storage

    Ohne C++11 kann man sich auch selbst so ein Template schreiben - wenn man sich damit zufrieden gibt dass es für eine bestimmte Plattform funktioniert ist das recht einfach.
    Dann schnappt man sich die Adresse von dem Ding, und erstellt mit placement-new dort das gewünschte Objekt. Zum Löschen darf man den Destruktor direkt über den Passenden Zeiger aufrufen (" ptr->~B(); ").

    Und natürlich kannst du boost::variant verwenden. So lange du den Typ nur beim Initialisieren des Variants festlegst und danach nie mehr änderst "lebt" das enthaltene Objekt auch direkt "im" Variant.
    Und falls du den Variant nicht gleich in der Initializer-List passend initialisieren kannst, kannst du das ganze noch mit boost::optional kombinieren.



  • Zisko schrieb:

    Hallo zusammen,

    ich programmiere auf einem stark ressourcenlimitierten embedded system. Da der Zugriff auf den externen Speicher länger dauert, habe ich es bisher vermieden Objekte dynamisch anzulegen, damit alles auf dem internen Speicher landet.
    Der Code wird nun jedoch arg unübersichtlich, wenn mir weiterhin verwehrt bleibt Objekte dynamisch zur Laufzeit zu generieren und ihnen dann mitzuteilen von welchem Typ sie sind.

    Gibt es die Möglichkeit ein Objekt bspw. mit einem strategy pattern ohne new zu erzeugen, so dass es auf dem Stack landet?

    Viele Grüße
    Zisko

    was heißt externer Speicher? new reserviert Speicher im RAM, ich denke mal dafür wird der interne RAM verwendet.
    Normale Variablen landen am Stack, und der befindet sich auch im RAM.
    So gesehen sollte new, mal abgesehen vom Verwaltungsaufwand, nicht viel langsamer sein als Variablen am Stack. Zumindest der Zugriff sollte genau gleich schnell sein!

    Ansonsten reservier dir halt statisch ein großes Bytearray, das landet in der Data Sektion und sollte im RAM landen. Und dann definier dir dein eigenes new/delete welches in eben diesem Bytearray Speicher reserviert.



  • grob vereinfachtes RAM Layout, auch auf vielen embedded systems:

    text: Der ausführbare Programmcode
    data: statische und globale Variablen
    heap: zur dynamischen Speicherverwaltung
    stack: für Funktionsaufrufe und lokale Variablen
    

    Ich behaupte Mal, dass für stack und heap der gleiche Speicher verwendet.
    Ansosnten kannst du das vielleicht dem Compiler mitteilen.
    So gesehen dürfte das Zugreifen auf stack (lokale Variablen) und heap (dyn. Speicher) nicht viel Zeitunterschied machen.


  • Mod

    Wäre es nicht evtl. zweckmäßig, einen pseudo-free-store zu verwenden, der auf dem Stack liegt?
    In main wird einfach eine Funktion aufgerufen, die entsprechend auf dem Stack ein hinreichend großes Array anlegt und dann die Funnktion für den restlichen Programmablauf aufruft. Zugriff auf diesen Pseuo-free-store kriegt man dann über überladene new/delete-Operatoren (ggf. mit einer eigenen placement-Form, um die Standardfunktionen weiter nutzen zu können).


  • Mod

    Wäre es nicht evtl. zweckmäßig, einen pseudo-free-store zu verwenden, der auf dem Stack liegt?

    Meinst du so etwas in der Richtung:

    template<class T, std::size_t N>
    class static_allocator
    {
        typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type data[N];
        std::size_t m_size = 0;
    
    public:
    
        std::size_t available() const { return N - m_size; }
    
        template<typename ...Args>
        T* create(Args&&... args)
        {
            if( m_size == N )
             // Fehlerbehandlung
    
            return new(data+m_size++) T(std::forward<Args>(args)...);
        }
    
        ~static_vector() 
        {
            for(std::size_t pos = 0; pos < m_size; ++pos)
                static_cast<T*>(static_cast<const void*>(data+pos))->~T();
        }
    };
    

    (angepasster Code von cppreference, ungetestet)
    Allerdings bin ich mir im Unklaren, ob das überhaupt standardkonform ist.



  • Nö, das funktioniert so nicht da der Allocator selbst nicht den Speicher besitzen darf/kann.
    Mann könnte die Arena seperat anlegen dem Allocator eine Referenz auf diese mitgeben.

    Anzumerken ist aber dass das mit der Arena massive Probleme macht wenn Container regelmäßig Speicher freigeben & dafür neuen allozieren. (vector/string etc)



  • stark ressourcenlimitierten embedded system

    Benenne doch mal das System.


  • Mod

    Nö, das funktioniert so nicht da der Allocator selbst nicht den Speicher besitzen darf/kann.

    Das war auch kein serienreifer Allokator, sondern eine Richtungsangabe. Man kann den Pool statisch machen.

    Anzumerken ist aber dass das mit der Arena massive Probleme macht wenn Container regelmäßig Speicher freigeben & dafür neuen allozieren.

    Wieso das?
    Die Vergabe des Speichers muss (gerade auf einem Ressourcenknappen System) stark optimiert werden, um möglichst keine ungenutzten Lücken zu erzeugen.



  • camper schrieb:

    Wäre es nicht evtl. zweckmäßig, einen pseudo-free-store zu verwenden, der auf dem Stack liegt?
    In main wird einfach eine Funktion aufgerufen, die entsprechend auf dem Stack ein hinreichend großes Array anlegt und dann die Funnktion für den restlichen Programmablauf aufruft. Zugriff auf diesen Pseuo-free-store kriegt man dann über überladene new/delete-Operatoren (ggf. mit einer eigenen placement-Form, um die Standardfunktionen weiter nutzen zu können).

    aber ich frage nochmal: warum soll der Stack im "schnellen" internen Speicher liegen, während der Heap im "langsamen" externen Speicher liegt.
    Hier wäre doch mal der erste Ansatzpunkt.

    @Arcoth: Wieso über Templates?
    Definier doch eine eigene Version von new/new[]/delete/delete[]. Letztlich hast du dann 2 Funktionen die die Logik implementieren und diese können für alle Datentypen verwendet werden.
    Und die holen sich den Speicher von einem vorher reservierten Speicherbereich von der stack oder data Sektion - je nachdem was nun im "schnellen" Speicher landet.
    Um die ganzen Details wie CTOR und DTOR aufrufen kümmert sich der Compiler.
    Und die Strategie fürs Allokieren sollte schon etwas intelligenter sein. Um nur 2 Dinge zu nennen: es gibt Fragmentierung und Ausrichtung zu beachten.
    Ausrichtung ist auf vielen Systemen gegeben, wenn die zurückgegebene Adresse auf 4 oder 8 byte ausgerichtet ist.
    Und gegen Fragmentierung gibt es einige Algorithmen wie z.B. den Buddy Allokator.



  • Arcoth schrieb:

    Anzumerken ist aber dass das mit der Arena massive Probleme macht wenn Container regelmäßig Speicher freigeben & dafür neuen allozieren.

    Wieso das?
    Die Vergabe des Speichers muss (gerade auf einem Ressourcenknappen System) stark optimiert werden, um möglichst keine ungenutzten Lücken zu erzeugen.

    Naja, zb:

    std::string foo = "Hello";
    foo += " World!";
    

    Hier wird foo vermutlich beim Anhängen von " World!" wachsen müssen. In der Arena gibt es keine Möglichkeit richtig sinnvoll Speicher zurückzugeben also wird der Speicher für "Hello" brach liegen.

    Finde eine Arena extrem interessant wenn man viele kleine, gleichgroße Objekte erzeugt (zb. std::list, std::map etc) - dann kann man die freigegebenen Objekte einfach speichern und recyclen. Vor allem werden die Container dadurch sauschnell (die Objekte liegen ja hintereinander im Speicher) - hab mal multimap mit queue verglichen - multimap war mit einem Allocator locker 10x so schnell.

    Aber general-purpose taugt das imho reichlich wenig.



  • Ich stelle mir gerade vor, dass man in den Lücken sozusagen eine linked-list führen könnte, dann mehrere Bins für unterschiedliche Größen hat, in denen der erste freie Speicherblock dieser oder etwas größerer Größe vermerkt ist (also ein Zeiger auf den Node der Linked-List in der Lücke), und dann schaut man erstmal in den Bins bei einer Anfrage, wenn da nix ist, dann hängt man hinten an. Bei Freigabe ersetzt man dann den im passenden Bin vermerkten Wert, sodass man nicht erst die Liste langlaufen muss. Also wird der Speicher "von hinten wiederbenutzt". Irgendwie so, könnte sowas klappen?



  • Ja, so geht das. Nur leider macht malloc das genauso, man gewinnt also keine Performance. Für Performance nimmt man einfach eine Arena und nimmt die paar Löcher in Kauf um dafür mehr Speed zu kriegen.



  • Irgendwie hatte ich jetzt den Eindruck gewonnen, dass malloc dann doch etwas mehr zu tun hat, als ein popelige if != nullptr und ein paar writes... Zumal es doch auch blockieren kann in Multithreading-Umgebungen?


Anmelden zum Antworten