mikrocontroller.net

Forum: Compiler & IDEs C++ Mc gcc Object löschen


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: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mein Programm hat ein Array von Objekten, die mit
Object o=new Object();
erzeugt wurden.

Wie kann ich alle Objekte löschen, damit das Array mit neu erzeugten 
Objekten gefüllt werden kann?

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit
delete o;

Anders geht es nicht, auch wenn du vermutlich was anderes hören 
wolltest.

Oliver
P.S. Dein Code ist übrigens falsch

: Bearbeitet durch User
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
std::unique_ptr<Object> o_safe{o};

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Anders geht es nicht, auch wenn du vermutlich was anderes hören
>wolltest.

Gibt's da keine Problem mit der Garbage Collection?
Gibt es ein Delete auf einem STM32?

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Gibt's da keine Problem mit der Garbage Collection?

Welche Implementation benutzt du, welche Garbage Collection bietet? 
Keine mir bekannte C++ Implementation für Mikrocontroller macht das.

chris schrieb:
> Gibt es ein Delete auf einem STM32

Wenn es new gibt, ja. Ob das ganze sinnvoll ist ist eine andere Frage.

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Welche Implementation benutzt du, welche Garbage Collection bietet?
Weiß ich nicht.
Hier der nicht funktionierende Versuch auf einem ESP32:
class Test
{
  public:
    int id;
    Test(int identifier)
    {
      id=identifier;
    }
    
    void show()
    {
      Serial.print("Test ID: ");
      Serial.println(id);
    }
};

std::vector<Test> Tests;

void setup()
{
  Serial.begin(115200);
  
}

#define ANZAHL 4

void loop()
{
  for(int n=0;n<ANZAHL;n++)
  {
    Test t=new Test(n);
    Tests.push_back(t);
  }

  // hier alle löschen ....
  
  delay(3000);
}

Autor: acbacb (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Und dann gehst du halt hin und machst eine schleife in der du durch den 
Vector iterierst, delete auf die objekte aufrufst und danch clearst du 
den vector.

Autor: nfet (Gast)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
chris schrieb:
> Hier der nicht funktionierende Versuch auf einem ESP32

Was bedeutet nicht funktionierend?

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Hier der nicht funktionierende Versuch auf einem ESP32:

Nicht funktionieren wird wohl Compilerfehlermeldungen bedeuten. Die 
solltest du lesen und verstehen.

Und lies die Kapitel zu Pointern und new/delete in deinem C-Buch.

Oliver

Autor: M.K. B. (mkbit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oliver S. schrieb:
> Und lies die Kapitel zu Pointern und new/delete in deinem C-Buch.

Nein! Lies das Kapitel zu smarten pointern in einem aktuellen C++ Buch 
(>=C++11). In modernem C++ sollte man new/delete eigentlich nicht mehr 
verwenden müssen, abgesehen von einzelnen Spezialfällen.

Beispiel:
std::vector<std::unique_ptr<Test>> Tests;

for(int n=0;n<ANZAHL;n++)
{
  Tests.emplace_back(std::make_unique<Test>(n));
}

Tests.clear();

Vorteile:
- Es ist direkt ersichtlich, dass der vector die Lebenszeit 
kontrolliert.
- Bei einem clear des vector werden die Instanzen sauber zerstört.

Außerdem ist der Code sicherer, wenn eine Exception auftritt, weil am 
Ende der vector sich selbst um die Zerstörung kümmert, auch wenn du aus 
der Loopfunktion springst. In deinem Beispiel würde er dann beim 
schließen der Applikation zerstört werden.

Wenn du den vector nur in loop() benötigst, dann würde ich den vector 
auch nur lokal in der loop Funktion anlegen. Der Stack ist dabei kein 
Problem, weil die Daten des vector sowieso auf dem heap liegen.
Wenn dir außerdem vorher die endgültige Größe des vectors bekannt ist, 
dann kann du mit reserve schon mal den Speicher dafür anfordern. Das 
spart evtl. mehrfache Speicheranforderungen durch den vector, wenn die 
bisherige Anzahl von reservierten Elementen bei einem push_back 
überschritten wird.

: Bearbeitet durch User
Autor: M.K. B. (mkbit)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Welche Implementation benutzt du, welche Garbage Collection bietet?
> Keine mir bekannte C++ Implementation für Mikrocontroller macht das.

C++ kann für new/delete keine Garbage Collection verwenden, weil man 
sich in C++ darauf verlassen kann, das beim delete der Destruktor 
aufgerufen wird. Sonst würde RAII z.B. für mutexes nicht funktionieren 
(https://de.wikipedia.org/wiki/Ressourcenbelegung_ist_Initialisierung).

Ob der Speicher dann direkt wieder dem Heap zur Verfügung gestellt wird 
oder dies erst mit einer Garbage Collection erfolgt ist dann wieder eine 
andere Sache.

Autor: leo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
M.K. B. schrieb:
> oder dies erst mit einer Garbage Collection erfolgt ist

C/C++ haben keine Garbage Collection.

leo

Autor: M.K. B. (mkbit)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
leo schrieb:
> C/C++ haben keine Garbage Collection.

Um hier Missverständnisse zu vermeiden.

Aus Sicht von new/delete gibt es keine Garbagcollection. D.h. das Objekt 
wird direkt zerstört und damit auch der Destruktor aufgerufen.

Was ich meinte bezog sich auf die interne Implementierung des Heaps. 
Meines Wissens nach muss der Heap den Speicher nicht gleich bei delete 
wieder zu Verfügung stellen, sondern kann dies auch erst bei einer 
Garbagecollection tun.

Autor: leo (Gast)
Datum:

Bewertung
-2 lesenswert
nicht lesenswert
M.K. B. schrieb:
> sondern kann dies auch erst bei einer
> Garbagecollection tun.

Was verstehst du hier nicht: C/C++ haben keine Garbage Collection.

leo

Autor: mh (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
leo schrieb:
> M.K. B. schrieb:
>> sondern kann dies auch erst bei einer
>> Garbagecollection tun.
>
> Was verstehst du hier nicht: C/C++ haben keine Garbage Collection.
>
> leo

Aber C und C++ könnten einen GC in der Implementation von malloc nutzen 
und das ist es was M.K. B. geschrieben hat.

Autor: leo (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
mh schrieb:
> Aber C und C++ könnten einen GC in der Implementation von malloc nutzen

Aha. Beispiel bitte.

leo

Autor: mh (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
leo schrieb:
> Aha. Beispiel bitte.

Ich mache es nochmal deutlicher: KÖNNTE

Autor: M.K. B. (mkbit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mh schrieb:
> Aber C und C++ könnten einen GC in der Implementation von malloc nutzen
> und das ist es was M.K. B. geschrieben hat.

Genau!

leo schrieb:
> Aha. Beispiel bitte.

Ich kenne keine konkreten Beispiele, aber es ging bei meiner Aussage 
auch nur darum, dass es aus Sicht der Sprache prinzipiell erlaubt ist.

Ohne mich damit jetzt im Detail auszukennen, aber ein Prozess in einem 
Betriebssystem könnte z.B. erstmal lokal im Prozess das free ausführen. 
Erst wenn eine bestimmte Zeit oder ein anderes Kriterium erfüllt ist, 
dann würde der Prozess diesen Speicher an das Betriebssystem zurückgeben 
und damit stünde dieser dann auch anderen Prozessen zur Verfügung.

Autor: leo (Gast)
Datum:

Bewertung
-3 lesenswert
nicht lesenswert
mh schrieb:
> leo schrieb:
>> Aha. Beispiel bitte.
>
> Ich mache es nochmal deutlicher: KÖNNTE

Nein.
[ ] Du weisst, wie ein GC funktioniert?

leo

Autor: M.K. B. (mkbit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
leo schrieb:
> Nein.
> [ ] Du weisst, wie ein GC funktioniert?

Um es nochmal klarzustellen.

Kein GC aus Sicht von new/delete.
GC bei der Speicherverwaltung durch den Heap.

Autor: leo (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
M.K. B. schrieb:
> GC bei der Speicherverwaltung durch den Heap.

Gibt es nicht.

leo

Autor: 2⁵ (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
M.K. B. schrieb:
> GC bei der Speicherverwaltung durch den Heap.

Welches OS macht denn sowas? Eine MMU kann Speicherseiten ein- bzw. 
ausblenden. Dass ist aber kein GC.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
leo schrieb:
> Aha. Beispiel bitte.

https://www.hboehm.info/gc/

Der C++ Standard erlaubt definitiv ein "normales"  GC in der Sprache, 
ähnlich wie Java. Das benutzt nur keiner. Daher der verbreitete 
Irrglaube, C++ würde prinzipiell kein GC unterstützen.

chris schrieb:
> Hier der nicht funktionierende Versuch auf einem ESP32:

Das ist vorne und hinten falsch. Lies erstmal ein C++ Buch. C++ ist eine 
komplexe Sprache, die lernt man nicht mal eben mit ein paar Tutorials. 
Und überlege, ob du wirklich dynamische Speicherverwaltung brauchst. Die 
macht nämlich nur Sinn, wenn man Speicher zu verschiedenen Zeitpunkten 
unterschiedlich nutzen will, also z.B. mal 100 Test Objekte anlegen, 
dann wieder freigeben, dann 100 Test2 Objekte. Wenn eine solche 
abwechselnde Nutzung nicht gegeben ist, wird nach der Freigabe der 
Speicher gar nicht benutzt, wofür es kein Geld zurück gibt. Daher ist es 
oft besser, einfach fixe Arrays (keine Vektoren) der benötigten Größe 
anzulegen und nie was freizugeben.

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Die macht nämlich nur Sinn, wenn man Speicher zu
> verschiedenen Zeitpunkten unterschiedlich nutzen will,
> also z.B. mal 100 Test Objekte anlegen,
>dann wieder freigeben, dann 100 Test2 Objekte.

Das Programm soll folgendes tun:
1. es werden N-Testobjekte angelegt ( z.B. N=100 )
2. Die Testobjekte werden ein Zeit lang benutzt und eine Test.execute() 
Methode aufgerufen.
3. Der Speicher, den die Testobjekte belegen, muss wieder frei gegeben 
werden.
4. Goto 1.

Ich vermute, dass im Framework des ESP32 keine Garbage Collection 
implementiert ist. In C wäre das Ganze extrem einfach zu lösen.

1. man reserviert Speicher für die Objekte und merkt sich den Speicher 
Anfang
2. man legt Objekte an ( Strukturen )
3. wie oben ( 1-3)
4. Man setzt den Objektpointer zurück
5. Goto 1.

Aufwand, die Objekte zu löschen: Null

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> 3. Der Speicher, den die Testobjekte belegen, muss wieder frei gegeben
> werden.

Warum muss? Wofür wird der Speicher danach gebraucht?

chris schrieb:
> 4. Man setzt den Objektpointer zurück
>
> Aufwand, die Objekte zu löschen: Null

Haha, weil du sie gar nicht löschst! Wenn du nur den Pointer auf Null 
setzt, ist der Speicher noch belegt, und du kannst ihn nie wieder 
freigeben - ein Speicherleck. Wenn du den Speicher tatsächlich 
freigibst, über die free() Funktion, ist der Aufwand natürlich nicht 
null, denn die Funktion kann ggf. nicht trivial sein.

chris schrieb:
> In C wäre das Ganze extrem einfach zu lösen.

In C++ sogar noch einfacher dank unique_ptr. Allerdings muss man dazu 
schon ein bisschen wissen was man tut und nicht einfach irgendwas 
zusammen stoppen.

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Das Programm soll folgendes tun:
> 1. es werden N-Testobjekte angelegt ( z.B. N=100 )

Muss dafür wirklich jedes dieser Objekte einzeln dynamisch angelegt 
werden? Woraus besteht so ein Objekt? Wäre es ein Problem, wenn das 
Objekt kopiert werden müsste? Wird Polymorphie benötigt?

> 2. Die Testobjekte werden ein Zeit lang benutzt und eine Test.execute()
> Methode aufgerufen.
> 3. Der Speicher, den die Testobjekte belegen, muss wieder frei gegeben
> werden.

Muss jedes Objekt einzeln wieder freigegeben werden, oder reicht es, 
wenn nach den 100 Tests alle am Stück gelöscht werden?

> 4. Goto 1.
>
> Ich vermute, dass im Framework des ESP32 keine Garbage Collection
> implementiert ist. In C wäre das Ganze extrem einfach zu lösen.

Was macht es in C++ denn schwierig? Man kann es da genauso machen wie in 
C.

> 1. man reserviert Speicher für die Objekte und merkt sich den Speicher
> Anfang
> 2. man legt Objekte an ( Strukturen )

Wo? Wie? Oben nutzt du für jedes Objekt einzeln new. Die Entsprechung in 
C wäre, dass du für jede der Strukturen einzeln mit malloc() Speicher 
reservierst. In dem Fall müsstest du die auch alle einzeln wieder 
freigeben.

> 3. wie oben ( 1-3)
> 4. Man setzt den Objektpointer zurück
> 5. Goto 1.
>
> Aufwand, die Objekte zu löschen: Null

Verschwinden sie auf magischem Weg?

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> In C wäre das Ganze extrem einfach zu lösen...

Dr. Sommer schrieb:
> Das ist vorne und hinten falsch. Lies erstmal ein C++ Buch. C++ ist eine
> komplexe Sprache, die lernt man nicht mal eben mit ein paar Tutorials.

Vieles, was für C++ gilt, gilt auch für C,

Oliver

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Bevor man anfängt drauf los zu coden, sollte man sich erst mal 
überlegen:
- Haben alle Objekte den gleichen Typ, oder sollen auch abgeleitete 
Typen möglich sein (Polymorphie)? Sollen sogar komplett unterschiedliche 
Typen ohne gemeinsame Basisklasse möglich sein?
- Wird die Größe des Containers initial festgelegt, oder sollen Elemente 
einzeln hinzugefügt und einzeln gelöscht werden können? Wenn ja, wie 
oft? Wenn nein, steht die Größe beim Kompilieren schon fest oder erst 
zur Laufzeit?
- Wenn die Elemente gelöscht wurden, wofür wird der Speicher dann 
gebraucht? Nur wieder für das erneute Befüllen des selben Containers?
- Soll der Container nur von Anfang bis Ende durchgegangen werden, oder 
auch wahlfreier Zugriff per Index möglich sein?
- Soll eine Assoziation/Mapping erfolgen, d.h. Elemente z.B. anhand 
eines Namens o.ä. suchen, und nicht nur nach Index?
- Spielt die Reihenfolge eine Rolle?
- Müssen die Elemente alle direkt im Speicher hintereinander stehen 
(z.B. für DMA) oder können sie verteilt sein?
- Sind die Elemente simple Datenobjekte oder haben sie virtuelle 
Funktionen?

Siehe auch: https://stackoverflow.com/q/471432

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> Der C++ Standard erlaubt definitiv ein "normales"  GC in der Sprache,
> ähnlich wie Java. Das benutzt nur keiner. Daher der verbreitete
> Irrglaube, C++ würde prinzipiell kein GC unterstützen.

Hm. Ich würde es auch so formulieren, wie M.K.B;

M.K. B. schrieb:
> Kein GC aus Sicht von new/delete.
> GC bei der Speicherverwaltung durch den Heap.

Der Sprachstandard kennt keinen GC. Der definiert 
allocator-/deallocator-Funktionen, die nach im Standard definierten 
Regeln von von new und delete aufgerufen werden. Nach Aufruf von delete 
ist das Objekt nicht mehr existent, und der Speicher deallokiert.

Mehr definiert der Sprachstandard nicht. Der ganze Rest liegt in der 
Verantwortung der Implementierung.

Oliver

: Bearbeitet durch User
Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus
>Muss dafür wirklich jedes dieser Objekte einzeln dynamisch angelegt
>werden? Woraus besteht so ein Objekt? Wäre es ein Problem, wenn das
>Objekt kopiert werden müsste? Wird Polymorphie benötigt?

Die Testobjekte sind von einem gemeinsamen Objekt abgeleitet, aber ihr 
Speicherverbrauch ist unterschiedlich groß.

>> 2. Die Testobjekte werden ein Zeit lang benutzt und eine Test.execute()
>> Methode aufgerufen.
>> 3. Der Speicher, den die Testobjekte belegen, muss wieder frei gegeben
>> werden.

>Muss jedes Objekt einzeln wieder freigegeben werden, oder reicht es,
>wenn nach den 100 Tests alle am Stück gelöscht werden?

Die gesamte Testliste wird durch eine neue mit einer anderen Anzahl von 
Objekte ersetzt. Die alten werden nicht mehr gebraucht, die ganze Liste 
kann also in einem Stück gelöscht werden.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> aber ihr
> Speicherverbrauch ist unterschiedlich groß.

chris schrieb:
> In C wäre das Ganze extrem einfach zu lösen.

Jetzt bin ich gespannt, wie das "extrem einfach" in C geht. Viele 
unterschiedliche structs mit malloc() anlegen, Pointer darauf in ein 
Array, und danach alle structs und das Array mit free() freigeben? Dann 
stimmt aber

chris schrieb:
> Aufwand, die Objekte zu löschen: Null

nicht.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie schon oben angedeutet wurde, beschwert sich der C-Compiler nicht 
über ein fehlendes free(). Was die Programmierung natürlich sehr einfach 
machen kann ;)

Oliver

: Bearbeitet durch User
Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Mein Programm hat ein Array von Objekten, die mit
>
>
> Object o=new Object();
> 

Das erzeugt kein Array, sondern ein einziges Objekt von Typen Object. 
Das geht auch viel einfacher mit:
Object o;

C++ ist deutlich anders, als Java! Der "übliche" Weg ein Array von 
Objekten anzulegen, wäre dann entweder:
Object os[10];

oder
std::array< Object, 10 > os;

In C++ arbeitet man viel häufiger mit Werten ohne den Umweg über Zeiger, 
während in Java (fast) alles erst einmal nur ein Zeiger ist, der eben 
auch Null sein kann.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Um das jetzt mal etwas abzukürzen, hier ein komplettes Beispiel mit 
einer Basis-Klasse "Test" und 2 davon abgeleiteten, welche in einem 
vector abgelegt werden:
#include <iostream>
#include <memory>
#include <vector>

class Test {
  public:
    virtual ~Test() {}
    virtual void show () = 0;
};

class Test1 : public Test {
  public:
    Test1 (int i) : id (i) {}
    virtual void show () { std::cout << id << std::endl; }
  private:
    int id;
};

class Test2 : public Test {
  public:
    Test2 (float f) : id (f) {}
    virtual void show () { std::cout << id << std::endl; }
  private:
    float id;
};


constexpr std::size_t ANZAHL = 4;


int main () {
  std::vector<std::unique_ptr<Test>> Tests;
  // Speicher für alle Zeiger auf einmal anlegen
  Tests.reserve (ANZAHL);
  
  // Objekte einfügen
  for (std::size_t i = 0; i < ANZAHL/2; ++i)
    Tests.push_back(std::make_unique<Test1> (i));
  
  for (std::size_t i = ANZAHL/2; i < ANZAHL; ++i)
    Tests.push_back(std::make_unique<Test2> (i+0.3f));
    
  // Ausgeben
  for (auto& t : Tests)
    t->show ();
  
  // Alle löschen
  Tests.clear ();
  
  // Eins einfügen
  Tests.push_back (std::make_unique<Test1> (42));

  // Ausgeben (diesmal leer)
  for (auto& t : Tests)
    t->show ();
  
  // Bei Rückkehr der Funktion wird "Tests" und alle Inhalte automatisch gelöscht
}

vector und unique_ptr sorgen dafür dass alles gelöscht wird.

PS: Für Konstanten sollte man "constexpr" und kein "#define" nutzen. In 
C++ gibt es kaum einen Grund für #define, und #define kann diverse 
Probleme machen.

Autor: leo (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Dr. Sommer schrieb:
> leo schrieb:
>> Aha. Beispiel bitte.
>
> https://www.hboehm.info/gc/

Was genau hat eine externe Bibliothek mit den GC-Eigenschaften der 
Sprache zu tun?

leo

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
leo schrieb:
> Was genau hat eine externe Bibliothek mit den GC-Eigenschaften der
> Sprache zu tun?

Ist nur ein Beispiel. Der Standard vermerkt an verschiedenen Stellen 
etwas zur Garbage Collection, er ist praktisch aufwärtskompatibel, denn 
es ist nicht vollständig spezifiziert. Es wird aber in der Praxis nicht 
genutzt. Ist auch egal, denn der GCC für ESP kann das bestimmt nicht.

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr.Sommer
>Um das jetzt mal etwas abzukürzen, hier ein komplettes Beispiel

Danke für's konkret werden. Das nenne ich vorbildlich.

Hier das ganze getestet für's Arduino Framework des ESP32:
#include <iostream>
#include <memory>
#include <vector>

class Test {
  public:
    virtual ~Test() {}
    virtual void show () = 0;
};

class Test1 : public Test {
  public:
    Test1 (int i) : id (i) {}
    virtual void show () { Serial.print("test1 int:");Serial.println(id); }
  private:
    int id;
};

class Test2 : public Test {
  public:
    Test2 (float f) : id (f) {}
    virtual void show () { Serial.print("test2 float:");Serial.println(id); }
  private:
    float id;
};


constexpr std::size_t ANZAHL = 4;

//make_unique is an upcoming C++14 feature and thus might not be available on your compiler,
//even if it is C++11 compliant.
//https://stackoverflow.com/questions/24609271/errormake-unique-is-not-a-member-of-std
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}


void setup()
{
  Serial.begin(115200);
}

void loop()
{
  std::vector<std::unique_ptr<Test>> Tests;
  // Speicher für alle Zeiger auf einmal anlegen
  Tests.reserve (ANZAHL);
  
  // Objekte einfügen
  for (std::size_t i = 0; i < ANZAHL/2; ++i)
    Tests.push_back(make_unique<Test1> (i));
  
  for (std::size_t i = ANZAHL/2; i < ANZAHL; ++i)
    Tests.push_back(make_unique<Test2> (i+0.3f));
 
  Serial.print("FreeHeap mit Objekten:");
  Serial.println(ESP.getFreeHeap());  
  // Ausgeben
  for (auto& t : Tests)
    t->show ();
  
  // Alle löschen
  Tests.clear ();

  Serial.print("FreeHeap Objekte gelöscht:");
  Serial.println(ESP.getFreeHeap());  
  
  // Eins einfügen
  Tests.push_back (make_unique<Test1> (42));

  // Ausgeben (diesmal leer)
  for (auto& t : Tests)
    t->show ();
  
  delay(3000);
}

Jetzt bin ich mal gespannt, wie sich das ganze laufzeitmässig mit C 
schlägt.
Die Test sollen nämlich im Mikrosekundenbereich und so schnell wie 
möglich laufen ( keine prints )

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> //make_unique is an upcoming C++14 feature

"Upcoming"...

chris schrieb:
> Jetzt bin ich mal gespannt, wie sich das ganze laufzeitmässig mit C
> schlägt.

Vermutlich etwas langsamer.

chris schrieb:
> Die Test sollen nämlich im Mikrosekundenbereich und so schnell wie
> möglich laufen ( keine prints )

Du bist lustig! new/malloc und delete/free sind natürlich eher langsam. 
Für  Echtzeit ist das natürlich vollkommen ungeeignet. Was machst du 
wenn kein Speicher mehr da ist? Einfach abstürzen lassen?

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Jetzt bin ich mal gespannt, wie sich das ganze laufzeitmässig mit C
> schlägt.

Dazu müsste man mal analysieren, was das Program oben macht....

Ok, gibt 11 Zahlen auf der seriellen Schnittstelle aus. Das bekommt man 
bestimmt auch in C "vernünftig" hin.

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr.Sommer
>Du bist lustig! new/malloc und delete/free sind natürlich eher langsam.
>Für  Echtzeit ist das natürlich vollkommen ungeeignet.

Es geht natürlich um die noch zu erschaffende "execute" Methode der 
Testklassen und nicht die Instantiierung der Objekte.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
chris schrieb:
> Es geht natürlich um die noch zu erschaffende "execute" Methode der
> Testklassen und nicht die Instantiierung der Objekte.

Dann ist die Laufzeit des bisher diskutierten Codes also irrelevant und 
somit C vs. C++ auch nicht von Bedeutung?

Autor: Dirk K. (merciless)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Wie kann ich alle Objekte löschen, damit das Array mit neu erzeugten
> Objekten gefüllt werden kann?
Warum müssen die Objekte gelöscht werden?
Man kann auch Objekt-Pooling verwenden...

https://de.wikipedia.org/wiki/Objektpool

merciless

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Um mal konkret zu werden ;-)
#include <iostream>
#include <memory>
#include <vector>

class Test {
  public:
    virtual ~Test() {}
    virtual void show () = 0;
    virtual void execute () = 0;
};

class Test1 : public Test {
  public:
    Test1 (int i) : id (i) {}
    virtual void show () { Serial.print("test1 int:");Serial.println(id); }
    virtual void execute () { id++; }
  private:
    int id;
};

class Test2 : public Test {
  public:
    Test2 (float f) : id (f) {}
    virtual void show () { Serial.print("test2 float:");Serial.println(id); }
    virtual void execute () { id*=3; }
  private:
    float id;
};


constexpr std::size_t ANZAHL = 4;

//make_unique is an upcoming C++14 feature and thus might not be available on your compiler,
//even if it is C++11 compliant.
//https://stackoverflow.com/questions/24609271/errormake-unique-is-not-a-member-of-std
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}


void setup()
{
  Serial.begin(115200);
}



void loop()
{
  std::vector<std::unique_ptr<Test>> Tests;
  // Speicher für alle Zeiger auf einmal anlegen
  Tests.reserve (ANZAHL);
  
  // Objekte einfügen
  for (std::size_t i = 0; i < ANZAHL/2; ++i)
    Tests.push_back(make_unique<Test1> (i));
  
  for (std::size_t i = ANZAHL/2; i < ANZAHL; ++i)
    Tests.push_back(make_unique<Test2> (i+0.3f));

  Serial.print("FreeHeap mit Objekten:");
  Serial.println(ESP.getFreeHeap());  
  // Ausgeben
  for (auto& t : Tests)
    t->show ();

  const uint32_t numListExecutions=100000;
  uint32_t startTime_us=micros();
  for(int n=0;n<numListExecutions;n++)
  {
    for (auto& t : Tests)
    t->execute ();
  }
  uint32_t stopTime_us=micros();
  Serial.print("average execution time per object: ");
  Serial.print((double)(stopTime_us-startTime_us)/numListExecutions/ANZAHL);Serial.println(" us");
  
    
  // Alle löschen
  Tests.clear ();

  Serial.print("FreeHeap Objekte gelöscht:");
  Serial.println(ESP.getFreeHeap());  
  
  // Eins einfügen
  Tests.push_back (make_unique<Test1> (42));

  // Ausgeben (diesmal leer)
  for (auto& t : Tests)
    t->show ();
  
  delay(3000);
}

FreeHeap mit Objekten:373504
test1 int:0
test1 int:1
test2 float:2.30
test2 float:3.30
average execution time per object: 0.14 us
FreeHeap Objekte gelöscht:373600
test1 int:42

Falls der ESP 1Befehl/Zyklus erlaubt wären das:

>> FCPU=240e6;
>> timePerObject=0.14e-6;
>> numberOfCpuCommandsPerObject=timePerObject*FCPU
numberOfCpuCommandsPerObject =  33.600

Und ein Objekt braucht 96Byte/4 = 24Byte Speicher.

Autor: MaWin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Das Programm soll folgendes tun:
> 1. es werden N-Testobjekte angelegt ( z.B. N=100 )
> 2. Die Testobjekte werden ein Zeit lang benutzt und eine Test.execute()
> Methode aufgerufen.
> 3. Der Speicher, den die Testobjekte belegen, muss wieder frei gegeben
> werden.
> 4. Goto 1.
>
> Ich vermute, dass im Framework des ESP32 keine Garbage Collection
> implementiert ist. In C wäre das Ganze extrem einfach zu lösen.
>
> 1. man reserviert Speicher für die Objekte und merkt sich den Speicher
> Anfang
> 2. man legt Objekte an ( Strukturen )
> 3. wie oben ( 1-3)
> 4. Man setzt den Objektpointer zurück
> 5. Goto 1.
>
> Aufwand, die Objekte zu löschen: Null

Ich glaube, du hast C/C++ so gar nicht verstanden.

Es gibt in C malloc und free, man braucht nur 1 Aufruf um ein ganzes 
Array im noch freien heap Speicher zu reservieren, und wieder 
freizugeben.
New und delete von C++ tun dasselbe, so lange in der Klassenstruktur nur 
einfache Variablen enthalten sind, keine weiteren Objekte (also structs 
mit ihrer eigenem Constructor und Destructor), und können auch ein 
ganzes Array auf einen Schlag behandeln.

Im einfachsten Fall macht malloc/new:
void *alloc(size_t bytes)
{
  // einfacher Fall, keine bzw. leere freelist
  heaptop-=bytes+sizeof(bytes);
  *(size_t *)heaptop=bytes;
  return heaptop+sizeof(bytes);
}
void free(void * ptr)
{
  if(ptr==heaptop+sizeof(size_t))
     heaptop+=*(size_t *)heaptop;
  else // aufwändiger
}

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
MaWin schrieb:
> Ich glaube, du hast C/C++ so gar nicht verstanden.
>
> Es gibt in C malloc und free, man braucht nur 1 Aufruf um ein ganzes
> Array im noch freien heap Speicher zu reservieren, und wieder
> freizugeben.

Und wie allokierst du ein Array aus lauter unterschiedlich großen 
Elementen?

chris schrieb:
> Die Testobjekte sind von einem gemeinsamen Objekt abgeleitet, aber ihr
> Speicherverbrauch ist unterschiedlich groß.

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Um mal konkret zu werden ;-)

Konkret wird es erst, wenn Du weist, welches Problem Du eigentlich lösen 
möchtest.

> average execution time per object: 0.14 us

Absolute irrelevant, wenn in diesen 140ns kein Problem gelöst wurde. 
Dafür ist das noch viel zu viel Zeit.

> Und ein Objekt braucht 96Byte/4 = 24Byte Speicher.

Wer mist, mist Mist. Sowohl `sizeof( Test1 )`, also auch `sizeof( Test2 
)` werden wohl 8 sein. Ist aber auch egal, weil Du nicht weißt, was 
`ESP.getFreeHeap()` überhaupt für einen Wert zurück gibt.

Wenn das wirklich wichtig wäre, dann verzichtest Du am besten auf die 
Verwendung des heaps.

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:

> Und wie allokierst du ein Array aus lauter unterschiedlich großen
> Elementen?

Der OP hatte "unterschiedlich groß" eigentlich nie erwähnt. Aber: Man 
könnte die dynamischen Typen alle hinter einander legen und dazu noch 
einen Array mit Zeigern auf den statischen Typen, um die Anfangsadressen 
zu bekommen.

Autor: MaWin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Und wie allokierst du ein Array aus lauter unterschiedlich großen
> Elementen

Grundlagen der Programmierung: Ein Array besteht aus gleichartigen 
Elementen.

Bei unterschiedlich grossen kann man nicht berechnen, dass das dritte 
Element bei 2+sizeof(element) steht.

Man braucht Pointer, geht aber auch, gern gemacht um strings in Worte zu 
zerlegen oder so:
element ** mem=malloc(elementanzahl*sizeof(element *)+elementgesamtgrösse);
for(n=0,ptr=mem+elementanzahl;n<elementanzahl;n++)
{
   mem[n]=ptr; ptr+=elementgrösse(n);
}
Da reicht dann 1 free um alle loszuwerden.

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Torsten R. schrieb:
> Der OP hatte "unterschiedlich groß" eigentlich nie erwähnt.

Hä? Ich hab doch die Stelle, wo er das erwähnt hat, zitiert.

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Torsten R. schrieb:
>> Der OP hatte "unterschiedlich groß" eigentlich nie erwähnt.
>
> Hä? Ich hab doch die Stelle, wo er das erwähnt hat, zitiert.

Sorry, habe ich übersehen!

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier ein Versuch mit einer Objektliste fester Länge.

Meine Vermutung war, dass die "execute"-Schleife etwas schneller ist und 
das die verkehrte Reihenfolge beim Löschen der Objekte zu Problemen 
führt.
Aber scheinbar bleibt alles wie beim vorigen Beispiel. Die 
Ausführungszeit ist gleich und mit dem "delete" der Objekte scheint es 
wohl auch kein Problem zu geben.

#include <iostream>
#include <memory>
#include <vector>

class Test {
  public:
    Test () {}
    virtual ~Test() {}
    virtual void show () = 0;
    virtual void execute () = 0;
};

class Test1 : public Test {
  public:
    Test1 (int i) : id (i) {}
    virtual void show () {
      Serial.print("test1 int:");
      Serial.println(id);
    }
    virtual void execute () {
      id++;
    }
  private:
    int id;
};

class Test2 : public Test {
  public:
    Test2 (float f) : id (f) {}
    virtual void show () {
      Serial.print("test2 float:");
      Serial.println(id);
    }
    virtual void execute () {
      id *= 3;
    }
  private:
    float id;
};


constexpr std::size_t ANZAHL = 4;


Test *Tests[ANZAHL];

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  Serial.print("FreeHeap ohne Objekte:");
  Serial.println(ESP.getFreeHeap());
  // Objekte einfügen
  for (std::size_t i = 0; i < ANZAHL / 2; ++i)
    Tests[i] = new Test1(i);

  for (std::size_t i = ANZAHL / 2; i < ANZAHL; ++i)
    Tests[i] = new Test2(i);

  Serial.print("FreeHeap mit Objekten:");
  Serial.println(ESP.getFreeHeap());
  //Ausgeben
  for (auto& t : Tests)
    t->show ();

  const uint32_t numListExecutions = 100000;
  uint32_t startTime_us = micros();
  for (int n = 0; n < numListExecutions; n++)
  {
    for (auto& t : Tests)
      t->execute ();
  }
  uint32_t stopTime_us = micros();
  Serial.print("average execution time per object: ");
  Serial.print((double)(stopTime_us - startTime_us) / numListExecutions / ANZAHL); Serial.println(" us");
  delay(3000);


  for (auto& t : Tests)
    delete(t);

}


average execution time per object: 0.14 us
FreeHeap ohne Objekte:373616
FreeHeap mit Objekten:373520
test1 int:0
test1 int:1
test2 float:2.00
test2 float:3.00
average execution time per object: 0.14 us
FreeHeap ohne Objekte:373616
FreeHeap mit Objekten:373520
test1 int:0
test1 int:1
test2 float:2.00
test2 float:3.00

Autor: chris (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Super Sache: Man kann den Compiler-Explorer verwenden, um den 
Assemblercode für verschiedene MCUs zu analysieren:

https://godbolt.org/

Autor: chris (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Tschuldigung ... die Dateiendung für das Bild fehlt.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Man kann den Compiler-Explorer verwenden, um den Assemblercode für
> verschiedene MCUs zu analysieren:

Mit objdump geht das auch lokal. Schalte mal die Optimierungen ein!

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier im Netz gibt es einen schönen Artikel zu malloc und 
Heap-Fragmentierung:
https://www.mikrocontroller.net/articles/Heap-Fragmentierung

Wird "malloc" bei C++ noch verwendet?
Ich suche nach einer Möglichkeit, in einem Objekt beim Instantiieren 
eine variable Speichergröße anzulegen und später das Objekt mitsamt 
Speicher wieder zu löschen.

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Wird "malloc" bei C++ noch verwendet?

Ja, "new" ruft das auf.

chris schrieb:
> Ich suche nach einer Möglichkeit, in einem Objekt beim Instantiieren
> eine variable Speichergröße anzulegen und später das Objekt mitsamt
> Speicher wieder zu löschen.

Also ein vector, genau wie gezeigt...

chris schrieb:
> Hier im Netz gibt es einen schönen Artikel zu malloc und
> Heap-Fragmentierung:

Das ist halt das Problem bei dynamischer Speicherverwaltung. Zum Glück 
braucht man die bei Embedded Systems auch fast nie...

Autor: Torsten R. (Firma: robitzki.de) (torstenrobitzki)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Wird "malloc" bei C++ noch verwendet?

Entweder verwendet "new", "malloc" direkt, oder eben etwas, das sehr 
ähnlich funktioniert.

> Ich suche nach einer Möglichkeit, in einem Objekt beim Instantiieren
> eine variable Speichergröße anzulegen und später das Objekt mitsamt
> Speicher wieder zu löschen.

Da Du das auf einer Hardware mit begrenztem Speicher laufen lassen 
möchtest, wäre der "übliche" Weg, Dir eine Maximalgröße zu überlegen und 
den Speicher genau für dieses Maximalgröße anzulegen.

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dr. Sommer
>Also ein vector, genau wie gezeigt...

Ist der geeignet, um in den verschiedenen Objekten ein "uin16_t" Array 
mit 10-4000 Werten anzulegen ( je nach geforderter Größe bei der 
Instantiierung )? Wird in einem Vector nicht eine "linked list" 
angelegt, die 3x soviel Speicher braucht?

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Ist der geeignet, um in den verschiedenen Objekten ein "uin16_t" Array
> mit 10-4000 Werten anzulegen

Ja natürlich.

chris schrieb:
> Wird in einem Vector nicht eine "linked list" angelegt, die 3x soviel
> Speicher braucht?

Nein, das macht std::list. vector legt ein Array aus zusammenhängendem 
Speicher an.

Autor: mh (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Wird in einem Vector nicht eine "linked list"
> angelegt, die 3x soviel Speicher braucht?

Wie kommt man auf so eine Idee?

Autor: chris (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Autor: mh (Gast)
>Wie kommt man auf so eine Idee?
Von hier:
https://de.cppreference.com/w/cpp/container/vector
"Der Speicherplatz des Vektors wird automatisch angepasst, er wird je 
nach Bedarf erweitert und verkleinert. Vektoren belegen in der Regel 
mehr Platz als statische Arrays, weil mehr Speicher zugewiesen wird um 
zukünftiges Wachstum zu behandeln."

Zugegeben, das mit der "linked list" war eine Vermutung. Aber zumindest 
hat "vector" einen Overhead. Die Frage ist, wie groß?

Autor: Dr. Sommer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> Aber zumindest hat "vector" einen Overhead. Die Frage ist, wie groß?

Ca 16 Bytes (einmalig). Siehe auch die Funktion std::vector::reserve...

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
chris schrieb:
> "Der Speicherplatz des Vektors wird automatisch angepasst, er wird je
> nach Bedarf erweitert und verkleinert. Vektoren belegen in der Regel
> mehr Platz als statische Arrays, weil mehr Speicher zugewiesen wird um
> zukünftiges Wachstum zu behandeln."
>
> Zugegeben, das mit der "linked list" war eine Vermutung. Aber zumindest
> hat "vector" einen Overhead. Die Frage ist, wie groß?

Das kommt drauf an. Wenn der Platz, den ein vector bietet, aufgebraucht 
ist, muss er reallokieren, d.h. einen neuen, größeren Speicherplatz 
belegen, alle bereits enthaltenen Daten rüberkopieren und dann den 
ursprünglichen Speicherbereich freigeben. Da man vermeiden möchte, dass 
das bei jedem einzelnen Hinzufügen eines Elements passiert, wird bei 
jeder Reallokation der Bereich üblicherweise nicht nur um ein einzelnes 
Element, sondern um einen bestimmten Faktor gegenüber dem vorherigen 
vergrößert, so dass noch Platz für weitere Elemente ist, bevor wieder 
neu reallokiert werden muss. Wie groß dieser Faktor ist, hängt von der 
Implementation ab. Manche verdoppeln z.B. immer. Während der 
Reallokation braucht der vector kurzzeitig dann also den dreifachen 
Platz (alter Speicherbereich + neuer, doppelt so großer Bereich).

: Bearbeitet durch User

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.

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