Hey Psycho,
Daten in einem Delphi Programm werden zZt entweder im Datensegment (globale Variablen), auf dem Stack (lokale Variablen, Rücksprungadressen und zT Parameter) oder auf dem sog. Heap (Objekte und Daten mit
New,
Getmem) abgelegt. Diese drei Bereiche sind allerdings in ihrer Struktur recht ähnlich: In ihnen deklarierte Variablen liegen linear im jeweiligen Speicherbereich hintereinander weg.
Deklarierst Du zB die beiden Variablen
Delphi-Quellcode:
var
iFoo: Integer;
iBar: Integer;
als globale Variablen, liegt die
iFoo zB bei der Adresse
@iFoo=$0044FC24 und
iBar bei
@iBar=$0044FC28 also bei
@iFoo+4, vier Bytes "weiter hinten" im Speicher, hier dem Datensegment. Die vier Bytes entsprechen der Größe eines
Integers unter Delphi.
Betrachtest Du nun ein Array, liegt jede Zelle des Arrays linear hinter ihrer Vorgängerzelle im Speicher. Bei dem Array
arFoobar
Delphi-Quellcode:
var
arFoobar: array[0..1] of Integer;
verhält es sich demnach genauso wie bei den beiden Variablen
iFoo und
iBar: Die Adresse der Zelle
arFoobar[1] liegt vier Bytes "weiter hinten" als
arFoobar[0].
Deklarierst Du nun Variablen hinter diesem Array, zB eine neue Variable
iBarfoo, ebenfalls vom Typ
Integer, würde der Code
arFoobar[2]:= 137;
so übersetzt werden (dies ist in der Praxis nur mit einer Hilfsvariablen möglich, weil das Litaral 2 vom Compiler gegen die Arraygrenzen geprüft wird), dass die Adresse der Variablen
iBarfoo berechnet und der Speicher an dieser Stelle manipuliert wird. In diesem Bsp ist also
arFoobar[2] identisch mit
iBarfoo!
Wenn Du Dir das Kompilat des Codes ansiehst, wirst Du erkennen, dass der Compiler lediglich Code generiert, der den Index (hier: 2) mit der Größe der Zelle des Arrays (hier: 4) multipliziert und zur Adresse der ersten Zelle des Arrays addiert (hier: @arFoobar=@arFoobar[0]).
Der Compiler kann die Größe des Datensegments zur Kompilierzeit berechnen und legt sie bei Delphiprogramm hart ins erstellte Binary ab. Das Betriebssystem kann deshalb ermitteln, ob ein Speicherzugriff gültig ist und löst andernfalls einen AccessVialation-Interrupt aus, der auf eine entspr. Delphi-
Exception gemappt wird.
Der Heap eines Programms ist relativ dynamsch und wird unter Delphi mithilfe eines speziellen Speichermanagers verwaltet. Hier kannst Du Dich leider nicht darauf "verlassen", dass Du eine entsprechende Fehlermeldung bekommst, wenn Du bspw. einen Speicherbereich eines Objekts verwendest, das bereits freigegeben ist.
Der Stack besitzt bei Delphiprogrammen zwar eine feste größe, die ist allerdings so gewählt, das sie (hoffentlich) nie erreicht wird, andernfalls gäbe es einen StackOverflow. Stattdessen gibt es einen "aktuelle Bereich" (Stackframe, Stackpointer, Basepointer), an den geschrieben wird. An dieser Stelle ist das Schreiben über die zulässigen Grenzen hinaus sehr gefährlich, weil Du zB die mit obigen Code die Rücksprungadressen verändern würdest (der Stack ist TopDown organisiert)!
Allgemein lässt sich sagen, dass zwar interessante Dinge wie zB das lesen privater Exemplarvariablen, Vorinitialisieren von lokalen Variablen, Verbiegen von Rücksprungadressen, etc. mit dieser und ähnlichen Techniken möglich sind. Trotzdem geschehen solche Dinge idR eher unbewusst und können dann zu unerwünschten Erscheinungen führen. Darüber hinaus ist das bewusste Manipulieren von Daten stark vom Compiler abhängig (Data Word Size, Byte Alignment) und ist in einer VM (Managed Code in Delphi for .net) nicht möglich.
Stattdessen bietet der Compiler mit Range Checking {$R}, I/O Checking {$I} und Overflow Checking {$Q} brauchbare Hilfsmittel, um ungewolltes Verändern von Daten zu verhindern.
HTH