Kurze Zusammenfassung vom vorherigen Thema: Statische Arrays werden über eine Range StartIndex..Endindex angegeben. Oftmals schreibt der Entwickler der nicht so oft mit Statischen Arrays arbeitet seine For schleifen allerdings
Code: Alles auswählen
for i:=0 to Length(Arr)-1 do
Arr[i] := XYZ;
Das Problem ist jetzt aber, ohne range Checks frisst das der Computer meist ohne zu murren auch wenn der Array nicht mit 0 anfängt. Das kann zu Fehlern führen die man nicht erwartet. Hier sind ein paar Beispiele (Getestet auf Mac OSX High Sierra, FPC 3.0.4, x86-64, ist hochgradig Plattform abbhängig):
Code: Alles auswählen
program test;
{$Mode ObjFPC}{$H+}
type
TFoo = class
private
FSomeValue: Integer;
FSomeArray: Array[17..20] of Integer;
public
constructor Create();
procedure Bar();
property SomeValue: Integer read FSomeValue;
end;
constructor TFoo.Create();
begin
FSomeValue := 42;
end;
procedure TFoo.Bar();
var i: Integer;
begin
for i:=0 to Length(FSomeArray)-1 do
FSomeArray[i] := -1;
end;
var
foo: TFoo;
bar: TFoo;
begin
foo := TFoo.Create;
bar := TFoo.Create;
try
bar.bar;
WriteLn(foo.SomeValue);
finally
foo.Free;
bar.Free;
end;
end.
Ausgabe: -1
Bei diesem Code wird die SomeValue Variable von Foo durch Code von Bar geändert, obwohl diese beiden Objekte eigentlich nichts voneinander wissen (dürfen und sollen), nur weil zufällig die beiden Objekte nebeneinander gelegt wurden.
Der Pascal Memory manager setzt memory blöcke verschiedener Klassen weit auseinander. Wenn man jetzt allerdings z.B. den C memory manager verwendet (was auch öfter mal vorkommt) wird es noch lustiger:
Code: Alles auswählen
program test;
{$Mode ObjFPC}{$H+}
uses
cmem;
type
TFoo = class
private
SomeOtherStuff1: array[0..10] of Integer;
FSomeValue: Integer;
SomeOtherStuff2: array[0..10] of Integer;
public
constructor Create();
property SomeValue: Integer read FSomeValue;
end;
TBar = class
private
FSomeArray: Array[20..30] of Integer;
public
constructor Create();
end;
constructor TFoo.Create();
begin
FSomeValue := 42;
end;
constructor TBar.Create();
var i: Integer;
begin
for i:=0 to Length(FSomeArray)-1 do
FSomeArray[i] := -1;
end;
var
foo: TFoo;
bar: TBar;
begin
foo := TFoo.Create;
bar := TBar.Create;
try
WriteLn(Foo.SomeValue);
finally
foo.Free;
bar.Free;
end;
end.
Ausgabe: -1
Jetzt kann das Objekt der Klasse Foo den Speicher von einem Objekt der Klasse Bar verändern. Selbst wenn Foo und Bar in komplett unterschiedlichen Units definiert wären wäre dies Möglich.
Wir können auch uns lokal den Speicher komplett kaputt machen, z.B. diese Endlosschleife:
Code: Alles auswählen
program test;
var
i: Integer;
arr: array[1..10] of Integer;
begin
for i:=0 to length(arr)-1 do
arr[i]:=-1;
end.
Und wenn wir eine Globale Variable haben können wir alle anderen Globalen Variablen kaputt machen:
SomeTestUnit.pas:
Code: Alles auswählen
Unit SomeTestUnit;
interface
var SomeVal: Integer;
implementation
end.
Test.pas:
Code: Alles auswählen
program test;
uses
SomeTestUnit;
var
arr: array[20..50] of Integer;
i: Integer;
begin
for i:=0 to Length(arr)-1 do arr[i] := -1;
WriteLn(SomeVal);
end.
Ausgabe: -1
------------------------------------------------------------------------------------------------------------
Worauf ich hinaus möchte ist, es kann sehr viel kaputt gehen. Um genau zu sein kann man durch die Startindex unterschiede jede beliebige stelle im Speicher treffen. All diese Beispiele oben laufen ohne Fehlermeldung ab (solange range Checks aus sind) und weder Klassen kapeslung bringt etwas, noch verschiedene Units, oder ähnliches bringen etwas. Das einzige was man dagegen tuen kann ist die Funktionen Low und High zu verwenden. Das Problem ist allerdings wer neu in Pascal ist (z.B. von Java kommt) weiß das natürlich nicht. In java iteriert man immer von 0 bis n-1. Dann reicht ein ungünstig gewählter Startindex, und etwas Pech im Memory mapping, und das was ich mir oben als Beispiele zusammengehackt hat wird Wirklichkeit. Und das plötzlich in einer Total fremden Klasse aus einer ganz anderen Unit ein wert geändert wurde, ist das finden dieses Fehlers sehr aufwendig. Wenn man noch Threading verwendet wird das ganze sogar noch weniger deterministisch, und das Fehler finden kann man praktisch komplett vergessen, da die resultierenden Fehler nicht reproduzierbar sind.
Man selbst kann zwar immer Low und High verwenden, allerdings ist man sicher das jeder der am Projekt mitwirkt dies auch tut.
Darum nun mein Appell an all die Pascal Programmierer hier, bitte fangt eure Arrays doch mit 0 an.