Vorsicht mit Arrayindices

Für alles, was in den übrigen Lazarusthemen keinen Platz, aber mit Lazarus zutun hat.

Vorsicht mit Arrayindices

Beitragvon Warf » 10. Apr 2018, 22:36 Vorsicht mit Arrayindices

In einem anderen Thema hier kamen wir auf die gefahren von Arrays mit startindices zu sprechen. Hiermit Lager ich das ganze in ein separates Thema aus.

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.
Warf
 
Beiträge: 985
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Timm Thaler » 10. Apr 2018, 23:35 Re: Vorsicht mit Arrayindices

Vergiss es!

Wenn ich Arrays immer mit Null anfangen müsste, könnte ich ja gleich so einen retardierten Quatsch wie C nehmen.

Oder wie es ein Freund oft auszudrücken pflegt: Man muss sich ja nicht nach unten orientieren.

Und warum zum Henker sollte ich range checks ausschalten?
Timm Thaler
 
Beiträge: 725
Registriert: 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded | 
CPU-Target: Raspberry Pi 3
Nach oben

Beitragvon m.fuchs » 10. Apr 2018, 23:39 Re: Vorsicht mit Arrayindices

Warf hat geschrieben:All diese Beispiele oben laufen ohne Fehlermeldung ab (solange range Checks aus sind)

In den Unittests sind sie aber nicht aus. Und schwupps schlägt der Test fehl.
Code: Alles auswählen
Range check error
Exception class: ERangeError
at   $000000000046A3F8 line 41 of testcase1.pas


So, welches Problem ist jetzt noch zu lösen?
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
m.fuchs
 
Beiträge: 1971
Registriert: 22. Sep 2006, 18:32
Wohnort: Berlin
OS, Lazarus, FPC: Winux (L 1.8.4, FPC 3.0.4) | 
CPU-Target: x86, x64, arm
Nach oben

Beitragvon siro » 11. Apr 2018, 07:29 Re: Vorsicht mit Arrayindices

Ich hab in meinem ganzen Leben noch kein Array gehabt, was nicht bei 0 angefangen hat. Programmiere schon 30 Jahre
wo benötigt man denn so etwas ?
Ich hätte auch zu viel "Angst" dass die Speicherzugriffe auf das Array "etwas" unkontrollert erfolgen könnten....
auf Deutsch, ich trau der Sache nicht übern weg und hab auch keine wirkliche Anwendung dafür

Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...
siro
 
Beiträge: 316
Registriert: 23. Aug 2016, 13:25
Wohnort: Berlin
OS, Lazarus, FPC: Windows 7 Windows 8.1 Windows 10 | 
CPU-Target: 64Bit
Nach oben

Beitragvon mischi » 11. Apr 2018, 08:02 Re: Vorsicht mit Arrayindices

siro hat geschrieben:Ich hab in meinem ganzen Leben noch kein Array gehabt, was nicht bei 0 angefangen hat. Programmiere schon 30 Jahre
wo benötigt man denn so etwas ?
Ich hätte auch zu viel "Angst" dass die Speicherzugriffe auf das Array "etwas" unkontrollert erfolgen könnten....
auf Deutsch, ich trau der Sache nicht übern weg und hab auch keine wirkliche Anwendung dafür

Siro

Wie ich schon im Vorgänger-Thread erwähnte, fangen in Fortran alle Arrays mit 1 an. Fortran wird zwar von den meisten als Nischensprache der Physik angesehen, aber es geht mir nur um ein Beispiel. Die Elemente einer Matrix in der Mathematik werden auch meistens mit 1 beginnend benannt. Die Stockwerke eines mehrgliedrigen Gebäudekomplexes an einem Hang fangen nicht unbedingt mit Erdgeschoss an. Auch bei anderen Bauten hat das Kellergeschoss die -1. Aus der Praxis gibt es da schon einige Beispiele, wo die Null nicht der naheliegende Start ist. Dass sich Arr[0] als Pointer-Ersatz durchgesetzt hat, sollte mit der Todesstrafe geahndet werden ;-)
MiSchi macht die fink-Pakete
mischi
 
Beiträge: 205
Registriert: 10. Nov 2009, 18:49
OS, Lazarus, FPC: macOS, 10.13, lazarus 1.8.x, fpc 3.0.x | 
CPU-Target: 32Bit/64bit
Nach oben

Beitragvon siro » 11. Apr 2018, 08:23 Re: Vorsicht mit Arrayindices

Ich habe mich bisher darauf verlassen und spekuliere auch damit, dass Array[0] IMMER der Start des Speichers ist. :oops:
Das liegt vermutlich daran, das ich früher ausschließlich und heute auch noch viel in Assembler programmiere.

Das Fortran mit 1 als index beginnt wuste ich nicht. Da muss man natürlich schon aufpassen.

Wenn das Verhalten "eindeutig" in der Programmiersprache festgelegt ist, dann sollte man sich natürlich auch auf die "richtige" Funktionsweise verlassen können und wenn man der Meinung ist,
dass es zur Übersichtlichkeit beiträgt, sollte man es wohl auch benutzen.
Viele Wege führen ja bekanntlich zum Ziel und es ist sicher eine "persönliche" Note wie ein Array angelegt bzw. verwaltet wird.

Wenn ich das richtig in Erinnerung habe, liegt bei der neueren Stringverwaltung ja auch einiges an "Verwaltung" unter dem
eigentlichen Array[0].

Es gibt also sicher berechtigte Anwendungen dafür.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...
siro
 
Beiträge: 316
Registriert: 23. Aug 2016, 13:25
Wohnort: Berlin
OS, Lazarus, FPC: Windows 7 Windows 8.1 Windows 10 | 
CPU-Target: 64Bit
Nach oben

Beitragvon Timm Thaler » 11. Apr 2018, 08:38 Re: Vorsicht mit Arrayindices

Monat 1..12
Tag 1..31
Wochentag 1..7 aus RTC
Heizung Profil Wochentag [1..7, 1..6]
Sensoren Kanäle 1..8, 9..16, 17..24
Pumpen 1..10
Phase U, I, P 1..3

Ja natürlich kann man das alles auf Null umrechnen. Man kanns aber auch lassen. Und funktioniert alles auch auf dem AVR.

Zum Beispiel werden Messkanäle lesbar als 1..24 per RS232 übertragen. Da muss ich im AVR von 0 auf 1 umrechnen, und am PC wieder von 1 auf 0. Nicht nur dass das unnötig ist, es sorgt auch regelmäßig für Verwirrung: Das was im AVR Kanal 0 ist, ist bei der Übertragung dann Kanal 1, im Programm am PC wieder Kanal 0 und beim Speichern in der CSV bekommt es wieder Kanal 1.

Nee, da bin ich froh weg von C zu sein und mit Pascal diesen Krampf nicht mehr zu haben. Eine Hochsprache soll sich ja dem Menschen anpassen, sonst kann ich gleich in Assembler schreiben.
Timm Thaler
 
Beiträge: 725
Registriert: 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded | 
CPU-Target: Raspberry Pi 3
Nach oben

Beitragvon Warf » 11. Apr 2018, 09:41 Re: Vorsicht mit Arrayindices

m.fuchs hat geschrieben:
Warf hat geschrieben:All diese Beispiele oben laufen ohne Fehlermeldung ab (solange range Checks aus sind)

In den Unittests sind sie aber nicht aus. Und schwupps schlägt der Test fehl.
Code: Alles auswählen
Range check error
Exception class: ERangeError
at   $000000000046A3F8 line 41 of testcase1.pas


So, welches Problem ist jetzt noch zu lösen?


Manchmal deaktiviert man die Range Checks aber auch, z.B. Wenn man nicht möchte das das einen false positive wirft: Move(arr[0], dst[0], len) für len = 0. z.B. In einem Daten parser oder Netzwerkprotokoll nahezu unvermeidliche

Da ist es oft einfacher rangechecks für diese Unit auszuschalten
Warf
 
Beiträge: 985
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon m.fuchs » 11. Apr 2018, 10:06 Re: Vorsicht mit Arrayindices

Du kannst sie ja auch deaktivieren, aber nicht in den Unittests. Damit sicherst die Korrektheit deines Quellcodes auf unterster Ebene. Wenn es da nicht zu Problemen kommt, dann läuft auch darüber alles.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
m.fuchs
 
Beiträge: 1971
Registriert: 22. Sep 2006, 18:32
Wohnort: Berlin
OS, Lazarus, FPC: Winux (L 1.8.4, FPC 3.0.4) | 
CPU-Target: x86, x64, arm
Nach oben

Beitragvon Warf » 11. Apr 2018, 11:15 Re: Vorsicht mit Arrayindices

m.fuchs hat geschrieben:Du kannst sie ja auch deaktivieren, aber nicht in den Unittests. Damit sicherst die Korrektheit deines Quellcodes auf unterster Ebene. Wenn es da nicht zu Problemen kommt, dann läuft auch darüber alles.


Nicht mal über Compiler Switches? Das würde mich nämlich stark wundern
Warf
 
Beiträge: 985
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon m.fuchs » 11. Apr 2018, 11:19 Re: Vorsicht mit Arrayindices

Ah, das klang missverständlich. Mit "du kannst sie ja auch deaktivieren" meine ich, dass du das gerne in deinem fertigen Programm machen kannst/darfst. In den Tests sollen sie aber immer an sein.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
m.fuchs
 
Beiträge: 1971
Registriert: 22. Sep 2006, 18:32
Wohnort: Berlin
OS, Lazarus, FPC: Winux (L 1.8.4, FPC 3.0.4) | 
CPU-Target: x86, x64, arm
Nach oben

Beitragvon Warf » 11. Apr 2018, 11:20 Re: Vorsicht mit Arrayindices

Timm Thaler hat geschrieben:Monat 1..12
Tag 1..31
Wochentag 1..7 aus RTC
Heizung Profil Wochentag [1..7, 1..6]
Sensoren Kanäle 1..8, 9..16, 17..24
Pumpen 1..10
Phase U, I, P 1..3

Ja natürlich kann man das alles auf Null umrechnen. Man kanns aber auch lassen. Und funktioniert alles auch auf dem AVR.

Zum Beispiel werden Messkanäle lesbar als 1..24 per RS232 übertragen. Da muss ich im AVR von 0 auf 1 umrechnen, und am PC wieder von 1 auf 0. Nicht nur dass das unnötig ist, es sorgt auch regelmäßig für Verwirrung: Das was im AVR Kanal 0 ist, ist bei der Übertragung dann Kanal 1, im Programm am PC wieder Kanal 0 und beim Speichern in der CSV bekommt es wieder Kanal 1.

Nee, da bin ich froh weg von C zu sein und mit Pascal diesen Krampf nicht mehr zu haben. Eine Hochsprache soll sich ja dem Menschen anpassen, sonst kann ich gleich in Assembler schreiben.


Aber auch eine kleine Verschiebung wie die um 1 kann schwere Fehler haben. Die oben gezeigte Endlosschleife ist da nur ein Beispiel
Du kannst damit Parameter, andere lokale Variablen sowie die rücksprungaddresse oder den Self Pointer überschreiben, da die lokalen Variablen auf dem Stack alle nah bei einander liegen
Warf
 
Beiträge: 985
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon m.fuchs » 11. Apr 2018, 11:25 Re: Vorsicht mit Arrayindices

Warf hat geschrieben:Aber auch eine kleine Verschiebung wie die um 1 kann schwere Fehler haben. Die oben gezeigte Endlosschleife ist da nur ein Beispiel
Du kannst damit Parameter, andere lokale Variablen sowie die rücksprungaddresse oder den Self Pointer überschreiben, da die lokalen Variablen auf dem Stack alle nah bei einander liegen

Das kann dir alles aber auch bei null-basierten Arrayindices passieren. Mit dem so beliebten Fehler:

Code: Alles auswählen
for i := 0 to Length(MyArray) do (*...*)
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
m.fuchs
 
Beiträge: 1971
Registriert: 22. Sep 2006, 18:32
Wohnort: Berlin
OS, Lazarus, FPC: Winux (L 1.8.4, FPC 3.0.4) | 
CPU-Target: x86, x64, arm
Nach oben

Beitragvon mischi » 11. Apr 2018, 12:00 Re: Vorsicht mit Arrayindices

siro hat geschrieben:Wenn ich das richtig in Erinnerung habe, liegt bei der neueren Stringverwaltung ja auch einiges an "Verwaltung" unter dem
eigentlichen Array[0].

Das ist auf jeden Fall immer von der Implementierung im Compiler abhängig. Deshalb würde ich so etwas immer vermeiden, wie der Teufel das Weihwasser. Eigentlich gehört da zumindest eine entsprechende Kommentar-Notiz dazu. Oft lässt sich das Problem "sauber" lösen, in dem man einen tatsächlichen Pointer verwendet.
MiSchi macht die fink-Pakete
mischi
 
Beiträge: 205
Registriert: 10. Nov 2009, 18:49
OS, Lazarus, FPC: macOS, 10.13, lazarus 1.8.x, fpc 3.0.x | 
CPU-Target: 32Bit/64bit
Nach oben

Beitragvon mischi » 11. Apr 2018, 12:02 Re: Vorsicht mit Arrayindices

m.fuchs hat geschrieben:
Code: Alles auswählen
for i := 0 to Length(MyArray) do (*...*)

Das hat stinkt so nach C, dass eigentlich jedem ordentlichen Pascal-Programmierer schlecht werden müsste ;-)
MiSchi macht die fink-Pakete
mischi
 
Beiträge: 205
Registriert: 10. Nov 2009, 18:49
OS, Lazarus, FPC: macOS, 10.13, lazarus 1.8.x, fpc 3.0.x | 
CPU-Target: 32Bit/64bit
Nach oben

» Weitere Beiträge siehe nächste Seite »
Nächste

Zurück zu Sonstiges



Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste

porpoises-institution
accuracy-worried