Seite 1 von 1

*pointer auf gelöschtes Element - warum funktioniert das?

Verfasst: 09.08.2020 06:30
von diceman
Hallo ihr Lieben,
Kann mir jemand erklären, warum der folgende Code funktioniert?

Code: Alles auswählen

Structure FOO
	index.i
EndStructure
NewList foo.FOO()
Define *this.FOO

For a = 1 To 10
	AddElement(foo())
	foo()\index = a
Next

ForEach foo()
	*this = @foo()
	DeleteElement(foo())
	ChangeCurrentElement(foo(),*this)
	Debug foo()\index
Next
Ich initialisiere einen *pointer für strukturierte Elemente.

Ich erstelle 10 Elemente und nummeriere diese durch.
Ich lasse die Liste durchlaufen - am Anfang jedes Durchlaufes speichere ich die Adresse des aktuellen Elementes mit meinem Pointer. Dann lösche ich das aktuelle Element, aber am Ende des Durchlaufes ist es mir trotzdem gestattet, das soeben gelöschte Element mittels ChangeCurrentElement() und meinem *pointer wieder herzustellen.

1. Frage:
Warum funktioniert das?

2. Frage:
Ist das safe? Also wenn es mir nur darum geht, am ENDE des Durchlaufes da weiterzumachen, wo ich am Anfang war, ganz egal was in der Mitte passiert - ist das ein legitimer Trick, oder kann mir das unter bestimmten Voraussetzungen um die Ohren fliegen?

Vielen Dank! :)

Re: *pointer auf gelöschtes Element - warum funktioniert das

Verfasst: 09.08.2020 07:38
von #NULL
Das gelöschte Element darfst du nicht mehr verwenden oder ansprechen. Der Speicherbereich könnte theoretisch schon für andere Dinge verwendet werden (z.b. für ein anderes Element bei AddElement()), selbst wenn er noch die Daten des Listenelements enthält. ChangeCurrentElement() geht davon aus, dass da ein gültiges Listenelement liegt und liest von dort einen Zeiger zur zugehörigen Liste aus, und die Liste selbst gibt es ja noch. Dann wird halt in der Liste selbst das current element gesetzt auf diesen Zeiger zum Speicherbereich des Elements. Dass das Element bereits gelöscht wurde weiß PB eventuell nicht. Vermutlich werden Elemente 'gelöscht' in dem einfach die next und previous Zeiger der umliegenden Elemente mit einander verknüpft werden statt auf dieses alte Element zu zeigen. Das gelöschte Element kommt somit nicht mehr in der Listensequenz vor. die next/prevous Zeiger des gelöschten Elementes zeigen aber vermutlich noch auf die anderen umliegende Elemente der Liste.
Siehe hier bei Element 'tmp'
Bild

Re: *pointer auf gelöschtes Element - warum funktioniert das

Verfasst: 09.08.2020 09:21
von Nino
diceman hat geschrieben:*pointer auf gelöschtes Element - warum funktioniert das?
Wie #Null schon schrieb, funktioniert das nur manchmal zufällig. Man kann sich keinesfalls darauf verlassen!
Hilfe zu ChangeCurrentElement() hat geschrieben:Das neue Element muss ein Zeiger (Pointer) auf ein anderes in der Liste existierendes Element sein.
Zeigt der Pointer jedoch auf ein nicht existierendes Listenelement, so ist das Verhalten undefiniert.

Re: *pointer auf gelöschtes Element - warum funktioniert das

Verfasst: 09.08.2020 11:58
von diceman
Super erklärt.
Vielen Dank! :allright:

Das Problem war in meinem Game-Projekt, daß ich in der monsterTurn()-Routine durch alle Monster iteriere, die sich bewegen, und nach jedem Schritt wird gecheckt, ob das Monster noch am Leben ist. Und sobald monster()\hp < 0 wird die killMonster()-Routine aufgerufen, welche nicht nur das Element löscht, sondern auch das monsterMap()-Array wieder freigibt.
So weit so gut. Allerdings kann der Tod eines Monsters mit einem Aufruf der Animations-Routine zusammenhängen, wenn ein Monster z.B. in ein Loch fällt, und da dann zwischendurch in der Darstellungsroutine die Monster-Liste bereits ganz ans Ende gerattert ist, muß ich das aktuelle Element wieder herstellen, bevor ich mit Next zum nächsten Monster in der ForEach-Schleife übergehen darf ... /:->
Habe den Code jetzt so geändert, daß Monster zu den regulären \status-Parametern (stateChasing, stateFleeing, stateSleeping, stateWaking) auch noch einen \status = stateDead) haben können - und wenn ein Monster stirbt, wird das Element zunächst nicht entfernt, sondern nur der \status des Monsters auf stateDead gesetzt. Dann, ganz am Ende, der Schleife wechsele ich mit ChangeCurrentElement() auf das aktuelle Monster und rufe einmal checkIfDead(@monster()) auf und lösche es da evtl. endgültig. Und da dies die potentiell letzte Operation in der Schleife ist, befinde ich mich auch nach dem Löschen noch an der korrekten Position.
Ein Monster stirbt also zweimal - einmal virtuell (dann werden bereits alle maps() intern aktualisiert, auch die Darstellungsroutine überspringt Monster mit \status = stateDead) und kurz darauf noch einmal richtig, wenn es aus dem Speicher entfernt wird.
:coderselixir: :coderselixir: :coderselixir:

Gut, daß ich nachgefragt habe ... ihr habt mir wahrscheinlich ein paar graue Haare in der Zukunft erspart!

Re: *pointer auf gelöschtes Element - warum funktioniert das

Verfasst: 08.10.2020 19:24
von GPI
Das mit State-Death ist schon mal gut.

Ich würde aber aus prinzip fürs löschen eine eigene ForeEach-Schleife machen, den Status abfragen und das Element löschen. Dann kann dir so ein Fehler in Zukunft nicht mehr passieren.
Eventuell das Löschen in eine eigene Prozedur auslagern (man kann auch Listen als Parameter übergeben mit den Steuerwort list) und dann die Werte vor den Löschen auf "gelöscht" umsetzen. du kannst dann mittels "compilerif #pb_compiler_debugger" einen code einfügen, an günstiger Stelle überprüft, ob das "aktuelle" Element ein "Gelöscht"-Flag hat. Wenn ja, sofort calldebugger und den Code anhalten - das ist ein grober schwerer bug.