Seite 1 von 1

Vorsicht bei CopyStructure und übergreifende Speicher

Verfasst: 18.08.2011 04:22
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!

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Verfasst: 18.08.2011 10:48
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.

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Verfasst: 18.08.2011 11:46
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.

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Verfasst: 18.08.2011 13:50
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.

Re: Vorsicht bei CopyStructure und übergreifende Speicher

Verfasst: 18.08.2011 14:23
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)