Benutzerdefinierte Datentypen im Speicher



  • Hallo liebe C++ - Community,

    das reine Anlegen einer benutzerdefinierten Datenstruktur (wie ich es in dem folgenden Code in Zeile 7 getan habe) reserviert im Speicher ja erstmal keinerlei Speicher. Jetzt frage ich mich: warum? Und woher kennt der Compiler beispielsweise die Namen g1 und g2 der benutzerdefinierten Datenstruktur, wenn das Anlegen einer solchen eigentlich keinen Speicherplatz verbrauchen soll?

    struct Group 
    {
    int g1; 
    double g2;
    };
    
    Group g; 
    g.g1 = 6; 
    g.g2 = 4;
    

    Vielen Dank im Voraus!

    Mit freundlichen Grüßen,
    Beeblebrox



  • Beeblebrox schrieb:

    das reine Anlegen einer benutzerdefinierten Datenstruktur (wie ich es in dem folgenden Code in Zeile 7 getan habe) reserviert im Speicher ja erstmal keinerlei Speicher.

    Was?

    Natürlich beansprucht g in deinem Beispiel Speicher.



  • Erneut, hallo liebe Community.

    Habe noch ein Beispiel gefunden, bei der das Verhalten benutzerdefinierter Datentypen mich verwundert.

    struct ListNode
    {
    int value;
    ListNode* prev;
    ListNode* next;
    };
    
    ListNode n0, n1, n2;
    n0.value = 10; 
    n0.prev = 0; 
    n0.next = &n1;
    n1.value = 11; 
    n1.prev = &n0; 
    n1.next = &n2;
    n2.value = 12; 
    n2.prev = &n1; 
    n2.next = 0;
    

    In diesem Code in Zeile 11 wird dem ListNode Zeiger n0 eine Referenz auf das folgende Element n1 zugewiesen. Jetzt soll ja aber eine benutzerdefinierte Datenstruktur im Speicher bei Deklaration (siehe Zeile 😎 keinerlei Platz verbrauchen. Intern bedeutet die Zuweisung des Elements an den Zeiger allerdings, dass n0 auf den Speicher zeigt in dem n1 steht (bzw. das erste Element). Nur kennt der Compiler doch zum Zeitpunkt der Zuweisung weder den Namen der Variable n1 (da dies ja ansonsten Speicherplatz verbrauchen würde) noch den Speicherort, da n1 zu diesem Zeitpunkt noch nicht initialisiert wurde. Ich bin verwirrt.

    Vielen Dank!

    Mit freundlichen Grüßen,
    Beeblebrox



  • Beeblebrox schrieb:

    In diesem Code in Zeile 11 wird dem ListNode Zeiger n0 eine Referenz auf das folgende Element n1 zugewiesen.

    Das ist nicht ganz korrekt. Dem Zeiger n0.next wird die Adresse von n1 zugewiesen. Referenzen sind etwas anderes, wirf das nicht durcheinander.

    Beeblebrox schrieb:

    Jetzt soll ja aber eine benutzerdefinierte Datenstruktur im Speicher bei Deklaration (siehe Zeile 😎 keinerlei Platz verbrauchen.

    Woher nimmst du diesen Quatsch?

    Beeblebrox schrieb:

    Ich bin verwirrt.

    Ich hätte es nicht treffender ausdrücken können.



  • Hallo erneut.

    Natürlich beansprucht g in deinem Beispiel Speicher.

    Laut meinen Professoren und Tutoren, nein.
    Das reine Anlegen also Deklarieren wie in Zeile 7 soll keinerlei Speicher reservieren.

    Folgender Code:

    struct Bruch 
    { 
    int z; 
    int n; 
    };
    
    Bruch b; 
    b.z = 66;
    b.n = 89;
    int a[2] = {25, 29};
    double c = 9.2;
    double* p = &c;
    

    Führt zu folgendem Speicher:

    (ich weiß leider nicht wie man Bilder hochladen kann, ist der Link zu meinem Aufgabenblatt)

    http://gcsc.uni-frankfurt.de/simulation-and-modelling/lectures-courses/prg1-epr/exercises/PRG1_Blatt06.pdf

    Ganz oben, Tabellendarstellung des Speichers. Die benutzerdefinierte Datenstruktur Bruch b nimmt im Speicher bei Deklaration keinerlei Platz in Anspruch.

    Danke für die Hilfe!



  • Beeblebrox schrieb:

    Die benutzerdefinierte Datenstruktur Bruch b nimmt im Speicher bei Deklaration keinerlei Platz in Anspruch.

    Erstens: Der Code in Zeile 7 ist nicht nur eine Deklaration, sondern auch eine Definition.

    Zweitens: b belegt Speicher, und zwar durch seine Member, b.z und b.n. Dass b darüber hinaus keinen Speicher belegt, ist hier zwar richtig, aber erstens ist das nicht immer so, und zweitens ist es nicht relevant. Der Speicher, den die Member von b belegen, gehört natürlich zu dem Speicher, den b selbst belegt, dazu.



  • Beeblebrox schrieb:

    Ganz oben, Tabellendarstellung des Speichers. Die benutzerdefinierte Datenstruktur Bruch b nimmt im Speicher bei Deklaration keinerlei Platz in Anspruch.

    In dem PDF steht doch ganz klar:

    Wird eine neue Variable deklariert, so erhält sie den nächsten freien Speicherplatz (d.h. eins nach dem letzten bereits belegten Speicherplatz).

    Ich vermute irgendwie das es nicht um die Variable b geht, sondern um das struct Bruch in Zeile 1-5. Die Deklaration benötigt nämlich wirklich keinen Speicher, sondern hat nur einen Einfluss auf den Code der für das Anlegen und den Zugriff auf das struct und dessen Elemente generiert wird.



  • Hallo erneut!

    Zweitens: b belegt Speicher, und zwar durch seine Member, b.z und b.n.

    Und woher nimmt er die Information über die Beschaffenheit von b, wenn diese ja im Prinzip nicht gespeichert ist zum Zeitpunkt der Definition / Deklaration?

    Dass b darüber hinaus keinen Speicher belegt, ist hier zwar richtig, aber erstens ist das nicht immer so, und zweitens ist es nicht relevant.

    Die Relevanz entsteht aus dem Anspruch es verstehen zu wollen. Meine Aussage ist ja, dass diese Member eben zum Zeitpunkt der Definition / Deklaration nicht bekannt sein dürften, da diese Signatur ja nirgendwo gespeichert ist. Wann ist das denn nicht so?

    Vielen Dank für deine / eure Hilfe!



  • Beeblebrox schrieb:

    Und woher nimmt er die Information über die Beschaffenheit von b, wenn diese ja im Prinzip nicht gespeichert ist zum Zeitpunkt der Definition / Deklaration?

    Der Compiler weiß zur Compilezeit, wie groß jeder Member ist, und wie weit er von der Adresse des Bruchs selbst enfernt ist. Mehr braucht er nicht.

    Auch wenn erst zur Laufzeit feststehen sollte, wo im Speicher b liegt, so steht doch schon zur Compilezeit fest, wie weit b.z und b.n davon entfernt sind. Und das wird auch fest in das Programm eincompiliert.



  • Du mußt unterscheiden zwischen dem Speicher, den der Compiler während des Kompiliervorgangs (für sich) anlegt und dem Speicher, den das (vom Compiler erzeugte) Programm zur Laufzeit benötigt.

    Die ganzen Deklarationen muß der Compiler natürlich kennen (d.h. dafür Speicher für sich anlegen).
    Das Programm selbst belegt aber bei

    struct Bruch
    {
      int z;
      int n;
    };
    

    zur Laufzeit keinen Speicher, da keine Variable dafür angelegt wird.
    Erst bei

    Bruch b;
    

    erzeugt der Compiler Code dafür, daß das Programm zur Laufzeit den Speicher dafür anlegt (und dessen Standard-Konstruktor aufruft).



  • Ahh .. okay, das hat geholfen. Vielen Dank!

    Jetzt würde mich noch interessieren, welche Anweisungen genau während der Compilezeit und welche während der Laufzeit ausgeführt werden?

    Du mußt unterscheiden zwischen dem Speicher, den der Compiler während des Kompiliervorgangs (für sich) anlegt und dem Speicher, den das (vom Compiler erzeugte) Programm zur Laufzeit benötigt.

    Nochmal kurz in eigenen Worten, nur um sicherzugehen, dass ich das richtig verstehe:

    Das heißt also, dass Informationen wie die benutzerdefinierten Datenstrukturen (also deren Definition) in einem eigenen, während der Compilezeit angelegen Speicher abgelegt werden auf die während der Laufzeit zugegriffen wird?

    Voll cool, dass ihr euch hier mit mir Hohlbrot so auseinandersetzt. 😃



  • Beeblebrox schrieb:

    Ahh .. okay, das hat geholfen. Vielen Dank!

    Voll cool, dass ihr euch hier mit mir Hohlbrot so auseinandersetzt. 😃

    Solange man das Gefühl hat das der Gegenüber auch was lernt und die Antworten wengistens versucht zu verstehen, denke ich, dass es kein Problem darstellt.

    Man hat ja selbst auch irgendwann mal angefangen und dabei nicht immer gleich alles verstanden.



  • Beeblebrox schrieb:

    Das heißt also, dass Informationen wie die benutzerdefinierten Datenstrukturen (also deren Definition) in einem eigenen, während der Compilezeit angelegen Speicher abgelegt werden auf die während der Laufzeit zugegriffen wird?

    Das trifft es nicht ganz.

    Du darfst dir das nicht so vorstellen, dass irgendwo im Speicher der "Bauplan" der Klasse Bruch herumliegt. Das ist gar nicht notwendig. Im kompilierten Programm gibt es keine Datentypen und Variablen mehr, nur noch Adressen.



  • Beeblebrox schrieb:

    Nochmal kurz in eigenen Worten, nur um sicherzugehen, dass ich das richtig verstehe:

    Das heißt also, dass Informationen wie die benutzerdefinierten Datenstrukturen (also deren Definition) in einem eigenen, während der Compilezeit angelegen Speicher abgelegt werden auf die während der Laufzeit zugegriffen wird?

    Nein, die Information des Compilers wird hinterher verworfen, da sie nicht gebraucht wird.

    Der Compiler macht im Prinzip das was du auf dem Aufgabenblatt machen sollst. Er sammelt alle Variablen ein und ordnet ihnen eine oder mehrere Adressen (z.B. bei benutzerdefinierten Datentypen) zu. Jetzt hat der Compiler so eine Tabelle und macht aus der Zeile b.z = 66 einen Maschinenbefehl der Form "schreibe 66 in Adresse 1". Die Adresse 1 entspricht natürlich b.z aber das ist dem Prozessor egal. Der Compiler ersetzt überall b.z und nur b.z durch Adresse 1, hat somit eine eindeutige Zuordnung und es ist ziemlich egal ob der Wert ein Teil von einem benutzerdefinierten Typ ist oder nicht.

    Es gibt dann noch den Fall das man einen ganzen Typ kopiert

    Bruch b = ...;
    Bruch c = b;
    

    Da würde der Compiler z.B. b die Adressen 1/2 geben, c die Adressen 3/4 und entsprechend zwei Maschinenbefehle generieren "Schreibe Wert von Adresse 1 nach Adresse 3" und "Schreibe Wert von Adresse 2 nach Adresse 4".

    Zusammengefasst, der Compiler kennt die Struktur und generiert die entsprechenden kleinschrittigen Befehle das die Information über die Struktur gar nicht mehr benötigt wird.



  • Der Compiler macht im Prinzip das was du auf dem Aufgabenblatt machen sollst. Er sammelt alle Variablen ein und ordnet ihnen eine oder mehrere Adressen (z.B. bei benutzerdefinierten Datentypen) zu. Jetzt hat der Compiler so eine Tabelle und macht aus der Zeile b.z = 66 einen Maschinenbefehl der Form "schreibe 66 in Adresse 1". Die Adresse 1 entspricht natürlich b.z aber das ist dem Prozessor egal. Der Compiler ersetzt überall b.z und nur b.z durch Adresse 1, hat somit eine eindeutige Zuordnung und es ist ziemlich egal ob der Wert ein Teil von einem benutzerdefinierten Typ ist oder nicht.

    Vielen vielen Dank, ihr habt mir alle sehr geholfen und ich denke, dass ich es jetzt verstanden habe .. 😃


Log in to reply