Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Eintrag aus Array löschen funktioniert bei 64 bit nicht (https://www.delphipraxis.net/199660-eintrag-aus-array-loeschen-funktioniert-bei-64-bit-nicht.html)

DieDolly 9. Feb 2019 17:34

Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Ich benutze seit einer Ewigkeit diesen Codeschnipsel und er funktioniert mit x86.
Mit x64 funktioniert er genau 2x und dann bekomme ich eine ungültige Zeigeroperation. Der Input ist immer der gleiche. Ein String mit mehreren Teilen geteilt durch |, die ich mit Explode in ein Array packe. Das klappt alles bis ich den ersten Eintrag löschen möchte. Dann kommt der Fehler.

Delphi-Quellcode:
procedure DeleteArrayIndex(var AArray: TArray<string>; AIndex: Integer);
begin
 if AIndex > High(AArray) then
  Exit;
 if AIndex < Low(AArray) then
  Exit;
 if AIndex = High(AArray) then
  begin
   SetLength(AArray, Length(AArray) - 1);
   Exit;
  end;
 Finalize(AArray[AIndex]);
 System.Move(AArray[AIndex + 1], AArray[AIndex], (Length(AArray) - AIndex - 1) * SizeOf(string) + 1);
 SetLength(AArray, Length(AArray) - 1);
end;

if StringParts[0] = 'HEADER' then
 DeleteArrayIndex(StringParts, 0); // Fehler
Das Array ist immer mindestens 3 Einträge lang.

Uwe Raabe 9. Feb 2019 17:39

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Wofür ist denn das + 1 am Ende?

Delphi-Quellcode:
System.Move(AArray[AIndex + 1], AArray[AIndex], (Length(AArray) - AIndex - 1) * SizeOf(string) + 1);
Welche Delphi Version?

DieDolly 9. Feb 2019 17:52

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Rio. Der Code wurde einfach von irgendwoher vor Jahren mal kopiert.

Uwe Raabe 9. Feb 2019 22:41

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Der Code ist meiner Meinung nach falsch - eventuell nur falsch abgeschrieben.

Das Move erwartet im dritten Parameter die Anzahl Bytes. Hier wird aus unerfindlichen Gründen 1 Byte zuviel angegeben. Der korrekte Code müsste so lauten:

Delphi-Quellcode:
System.Move(AArray[AIndex + 1], AArray[AIndex], (Length(AArray) - AIndex - 1) * SizeOf(string));


Einfacher geht es aber mit

Delphi-Quellcode:
Delete(AArray, AIndex, 1);


Siehe: System.Delete

p80286 10. Feb 2019 08:38

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Ich vermute, er möchte noch eine trailing 0 mitschleppen. Das war vielleicht mal sinnvoll......

Gruß
K-H

HolgerX 10. Feb 2019 09:20

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Hmm..


Zitat:

Zitat von p80286 (Beitrag 1425168)
Ich vermute, er möchte noch eine trailing 0 mitschleppen. Das war vielleicht mal sinnvoll......

Gruß
K-H

Aber auch dann währe es falsch, da hier das Byte (versucht) kopiert wird, welches sich 'hinter' dem Array befindet.
Und dies kann irgendetwas sein.

Auch schreibt er dieses Byte dann 'hinter' das Array in einen Speicher, welcher nicht zum Array gehört und dann kann es knallen..
;)
(Edit: OK, knallen würde es hier nicht, da er das Array erst danach abschneidet.. Aber generell sollte mit Move vorsichtig umgegangen werden ;) )

Uwe Raabe 10. Feb 2019 09:58

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
Zitat:

Zitat von p80286 (Beitrag 1425168)
Ich vermute, er möchte noch eine trailing 0 mitschleppen. Das war vielleicht mal sinnvoll......

Das kann nicht mal zu Zeiten von string = string[255] funktioniert haben. Ein TArray<string> ist immer ein Array von Pointern und Sizeof(string) ist immer 4 (32 Bit) oder 8 (64 Bit). Das 1 Byte mehr macht hier definitiv nur Ärger. Es sorgt nämlich dafür, daß der letzte Array-Eintrag verfälscht wird.

Ich wäre in diesem Fall aber sowieso vorsichtig, was die Referenzzählung bei Strings betrifft. Die kann mit dem Move schon mal gehörig durcheinander geraten. Selbst bei korrektem Count zeigen nach dem Move die letzten beiden Array-Elemente auf denselben String. Ich glaube kaum, daß bei dem Move auch die Referenzzählung entsprechend angepasst wird. Beim folgenden SetLength allerdings schon. Dieses Beispielprogramm verdeutlicht die Problematik und zeigt, daß die Verwendung von Delete nicht nur einfacher, sondern auch sicherer ist.


Delphi-Quellcode:
program Project490;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

function StringRefCount(const Value: string): Integer;
var
  P: PInteger;
begin
  P := @Value[1];
  Dec(P, 2);
  Result := P^;
end;

procedure Main;
var
  arr: TArray<string>;
  I: Integer;
begin
  SetLength(arr, 10);
  for I := 0 to 8 do begin
    arr[I] := I.ToString;
  end;
  arr[9] := arr[8];

  Writeln('Verwendung von Move und SetLength');
  Writeln('8 und 9 zeigen auf denselben string, RefCount ist OK');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  Writeln('9: ', StringRefCount(arr[9]));
  Move(arr[1], arr[0], 9*SizeOf(arr[0]));
  Writeln('nach Move: 7,8 und 9 zeigen auf denselben string, aber RefCount is 2!');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  Writeln('9: ', StringRefCount(arr[9]));
  SetLength(arr, 9);
  Writeln('nach SetLength: RefCount für 9 wird runtergezählt.');
  Writeln('7 und 8 zeigen auf denselben string, aber RefCount ist 1!');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  arr[8] := '';
  Writeln('8 ist jetzt leer, RefCount wurde erniedrigt.');
  Writeln('7 zeigt immer noch auf diesen string, aber RefCount ist 0!');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  Writeln;
end;

procedure Main2;
var
  arr: TArray<string>;
  I: Integer;
begin
  SetLength(arr, 10);
  for I := 0 to 8 do begin
    arr[I] := I.ToString;
  end;
  arr[9] := arr[8];

  Writeln('Verwendung von Delete');
  Writeln('8 und 9 zeigen auf denselben string, RefCount ist OK');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  Writeln('9: ', StringRefCount(arr[9]));
  Delete(arr, 0, 1);
  Writeln('nach Delete: RefCount für 9 wird runtergezählt.');
  Writeln('7 und 8 zeigen auf denselben string, RefCount ist OK');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  arr[8] := '';
  Writeln('8 ist jetzt leer, RefCount wurde erniedrigt.');
  Writeln('7 zeigt immer noch auf diesen string, RefCount ist OK');
  Writeln('7: ', StringRefCount(arr[7]));
  Writeln('8: ', StringRefCount(arr[8]));
  Writeln;
end;

begin
  try
    Main;
    Main2;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Blup 13. Feb 2019 09:56

AW: Eintrag aus Array löschen funktioniert bei 64 bit nicht
 
So würde es funktionieren:
Delphi-Quellcode:
 Finalize(AArray[AIndex]);
 System.Move(AArray[AIndex + 1], AArray[AIndex], (Length(AArray) - AIndex - 1) * SizeOf(string));
 {letzten Eintrag auf nil setzen, damit keine Referenzzählung für den jetzt doppelten Eintrag bei SetLength erfolgt}
 System.FillChar(AArray[Length(AArray) - 1], SizeOf(string), 0); // oder Pointer(AArray[Length(AArray) - 1]) := nil;
 SetLength(AArray, Length(AArray) - 1);
Inzwischen ist das wie beschrieben einfacher zu lösen:
Delphi-Quellcode:
System.Delete(AArray, AIndex, 1);
.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz