Vorsicht bei CopyStructure und übergreifende Speicher

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7028
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Vorsicht bei CopyStructure und übergreifende Speicher

Beitrag von STARGÅTE »

Tachchen,

wie ich eben feststellen musste ist CopyStructure() keine Art von "MoveStructure" denn es taucht Folgendes Problem auf:

Folgende Situation: Ich habe eine Baum-Struktur mit Einträgen.
Nutze ich nun das CopyStructure() als MoveStructure indem ich tiefere Ebenen nach oben kopiere so werden die Nachkommen alle gelöscht.

Hier ein Beispiel:

Code: Alles auswählen

Structure Tree
	String.s
	Integer.i
	List Child.Tree()
EndStructure

Procedure DebugTree(*Tree.Tree, Space.i=2)
	Debug Space(Space)+*Tree\String
	ForEach *Tree\Child()
		DebugTree(*Tree\Child(), Space+2)
	Next
EndProcedure

Macro Start()
	Tree.Tree
	Tree\String = "Root"
	AddElement(Tree\Child()) : Tree\Child()\String = "Child.1"
	AddElement(Tree\Child()\Child()) : Tree\Child()\Child()\String = "SubChild.1.1"
	AddElement(Tree\Child()\Child()) : Tree\Child()\Child()\String = "SubChild.1.2"
	AddElement(Tree\Child()) : Tree\Child()\String = "Child.2"
	AddElement(Tree\Child()\Child()) : Tree\Child()\Child()\String = "SubChild.2.1"
	AddElement(Tree\Child()\Child()) : Tree\Child()\Child()\String = "SubChild.2.2"
EndMacro


Start()
Debug "Ausgangssituation:"
DebugTree(Tree)
Debug ""

CopyStructure(@Tree\Child(), @Tree, Tree)

Debug "Nach dem Kopieren von Child.2 nach Root:"
DebugTree(Tree)
Debug "ubs da fehlen ja die Kinder!"
Debug ""


Start()
Debug "noch mal Ausgangssituation:"
DebugTree(Tree)
Debug ""

CopyStructure(@Tree\Child(), @SaveTree.Tree, Tree)
CopyStructure(@SaveTree, @Tree, Tree)

Debug "Nach dem Kopieren von Child.2 nach Root:"
DebugTree(Tree)
Debug "OK, Kinder sind auch da"
Scheinbar wird nicht erst intern eine Kopie der Struktur erstellt, welche dann an der Zieladresse eingefügt wird, sonden sofort mit dem Überschreiben begonnen, was zur Folge hat, das während des kopierens auch die Quelle verändert wird.

Nun gut, also ist ein Zwischenspeichern nötig, wie:

Code: Alles auswählen

CopyStructure(@Tree\Child(), @SaveTree.Tree, Tree)
CopyStructure(@SaveTree, @Tree, Tree)
Blöderweise verursachte dieser Code trotzdem seltsamme IMAs die ich leider (noch) nicht in einem Beispiel reproduzieren konnte.
Es steht jedoch fest, das es auch noch nötig ist, vor dem Einfügen ein ClearStructure() auszuführen, um das Ziel vorher "zu bereinigen".
Da CopyStructure ja selbst das Ziel durch ein InitializeStructure() initialisiert, kommt es vermutlich sonst zu dopplungen.

Hier also mein sichererer Weg für ein CopyStructure:

Code: Alles auswählen

CopyStructure(@Tree\Child(), @SaveTree.Tree, Tree)
ClearStructure(@Tree, Tree)
CopyStructure(@SaveTree, @Tree, Tree)
Noch eine Wahrnung:
Wenn man sogar eine höhere Ebene nach unten Kopiert entsteht sogar eine Endlosschleife innerhalb des CopyStructure die binnen Millisekunden den RAM dicht macht!

Code: Alles auswählen

; WAHRUNG ! RAM-ÜBERLASTUNG IN WENIGEN MILLISEKUNDEN!
Structure Tree
  List Child.Tree()
EndStructure

Tree.Tree
AddElement(Tree\Child())

CopyStructure(@Tree, Tree\Child(), Tree)
; WAHRUNG ! RAM-ÜBERLASTUNG IN WENIGEN MILLISEKUNDEN!
Auch wenn in der Hilfe steht: "Diese Funktion ist nur für fortgeschrittene Anwender und sollte mit Vorsicht verwendet werden."
Sollte man hier noch diese Hinweise geben: bezüglich übergreifender Speicher!
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
GPI
Beiträge: 1511
Registriert: 29.08.2004 13:18
Kontaktdaten:

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Beitrag von GPI »

Nicht verwunderlich, wenn man sich das mal anschaut:

Code: Alles auswählen

Structure Tree
   String.s
   Integer.i
   List Child.Tree()
EndStructure

Debug SizeOf(tree)
;gibt 16 zurück
; 4 für String (das ist hier nur eine Adresse!)
; 4 für Integer
; 4 für List - prev Element
; 4 für List - next element
Es werden offensichtlich 8 Byte für "List" reserviert - meine Vermutung, je eine Adresse für nächstes und letztes Element

Des weiteren können hier erhebliche Speicherlecks bei deiner Handharbung auftreten. String.s in der Structure bewirkt nicht, das der String dort gespeichert wird, sondern, das dort eine Adresse abgelegt wird, wo der String dann steht. Wenn du jetzt das zweite Element über das erste bügelst, dann liegt der ursprüngliche String aus den ersten Element "verloren". Er verbraucht speicher, aber man kann nicht mehr drauf zugreifen, weil du die Adresse verloren hast. Zudem verweisen dann String aus Element 1 und 2 auf die gleiche Adresse, wenn du als in Element 1 was änderst, wird Element 2 mit geändert.

Deshalb steht in der Anleitung "nur für Fortgeschrittene" - man braucht da mehr Hintergrundwissen, was der Compiler macht.
CodeArchiv Rebirth: Deutsches Forum Github Hilfe ist immer gern gesehen!
freak
PureBasic Team
Beiträge: 766
Registriert: 29.08.2004 00:20
Wohnort: Stuttgart

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Beitrag von freak »

Das Verhalten ist ganz logisch: Du musst bedenken dass CopyStructure() nicht einfach nur die Daten der Struktur selber umkopiert sondern auch den Inhalt von Listen usw. dupliziert (sonst kann man ja CopyMemory() nehmen). Dazu wird erst die Zielstruktur freigegeben und dann der Inhalt der Quellstruktur kopiert.

Hier passiert also folgendes:

1. Fall:
- Du kopiert ein Element einer Liste in die Struktur die diese Liste beinhaltet.
- Der erste Schritt, das freigeben des Ziels, löscht auch die Liste in der sich deine Quelle befindet.
- Desshalt sind keine Kinder mehr da und evtl. bekommst du auch mal einen Speicherzugriffsfehler.

2. Fall:
- Du kopierst eine Struktur in das Element der Liste die sich in der Struktur befindet.
- Dazu muss rekursiv alles Kopiert werden was sich in der Struktur befindet, auch die Liste mit dem Zielelement.
- Nach dem Kopieren des Zielelementes als Kind ins neue Zielelement hat dein Baum aber schon 3 Stufen
- Beim kopieren der 3. Stufe entsteht eine 4. Stufe
- ... usw.

Rekursiv Bäume in sich selbst kopieren geht eigentlich immer schief. Das ist nicht speziell ein Problem von CopyStructure(). Wenn du wirklich nur den Inhalt der Strukturen praktisch verschieben willst, dann bist du mit CopyMemory() besser dran.
GPI hat geschrieben:Des weiteren können hier erhebliche Speicherlecks bei deiner Handharbung auftreten. String.s in der Structure bewirkt nicht, das der String dort gespeichert wird, sondern, das dort eine Adresse abgelegt wird, wo der String dann steht. Wenn du jetzt das zweite Element über das erste bügelst, dann liegt der ursprüngliche String aus den ersten Element "verloren". Er verbraucht speicher, aber man kann nicht mehr drauf zugreifen, weil du die Adresse verloren hast. Zudem verweisen dann String aus Element 1 und 2 auf die gleiche Adresse, wenn du als in Element 1 was änderst, wird Element 2 mit geändert.
Das ist falsch. CopyStructure() gibt den Zielspeicher frei und kopiert Strings und Listen mit. Das ist ja genau das Problem das STARGÅTE hat.
ullmann
Beiträge: 205
Registriert: 28.10.2005 07:21

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Beitrag von ullmann »

Eine detaillierte Erklärung in der Hilfe wäre toll. Um die Übersichtlichkeit zu wahren, könnte dies hinter einem Link
"Detaillierte Hinweise für fortgeschrittene Benutzer" eingeordnet werden.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7028
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Beitrag von STARGÅTE »

Rekursiv Bäume in sich selbst kopieren geht eigentlich immer schief. Das ist nicht speziell ein Problem von CopyStructure(). Wenn du wirklich nur den Inhalt der Strukturen praktisch verschieben willst, dann bist du mit CopyMemory() besser dran.
Jo, das ist mir auch schon bei XML usw. aufgefallen.
CopyMemory() kann ich aber nicht verwenden, da dann ja nicht das Ziel freigegeben wird.
Das kopieren wird zwar erfolg haben, da ja Listen, Elemente usw. eh außerhalb stehen und der Zugriff weiterhin gültig ist, aber die im Ziel befindliche Struktur wird nicht freigegeben, sodass u.u. Listen und Elemente übrig bleiben, die nicht zur Quelle gehören.

Daher weerde ich nun weiter die Lösung mit dem Zwischenzweichern nutzen:

Code: Alles auswählen

CopyStructure(*Quelle, *Sicherung, Struktur)
ClearStructure(*Ziel, Struktur)
CopyStructure(*Sicherung, *Ziel, Struktur)
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Antworten