Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Zugriffsverletzung bei Zeiger auf statisches Array (https://www.delphipraxis.net/201779-zugriffsverletzung-bei-zeiger-auf-statisches-array.html)

Codehunter 26. Aug 2019 09:52

Zugriffsverletzung bei Zeiger auf statisches Array
 
Hallo!

Ich versuche gerade, eine Klassenstruktur zu erfinden, die sich über ein statisches Array initialisieren lässt. Nur leider geht die Runtime nicht mit meiner Theorie konform und wirft eine AV.
Delphi-Quellcode:
unit Unit1;

interface

type
  TMyRecord = record
    A: Integer;
    B: Integer;
    C: Byte;
  end;

  TMyRecords = array of TMyRecord;
  PMyRecords = ^TMyRecords;

  TMyClass = class
  private
    FRecords: PMyRecords;
  protected
    function GetRecord(const AIndex: Integer): TMyRecord;
  public
    property Records[const AIndex: Integer]: TMyRecord read GetRecord; default;
  end;

  TMyClassA = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6)
    );
  public
    constructor Create;
  end;

  TMyClassB = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6),
      (A: 7; B: 8; C: 9)
    );
  public
    constructor Create;
  end;

implementation

{ TMyClass }

function TMyClass.GetRecord(const AIndex: Integer): TMyRecord;
begin
  Result := FRecords^[AIndex]; // <-- Hier die AV
end;

{ TMyClassA }

constructor TMyClassA.Create;
begin
  Records := @RECS;
end;

{ TMyClassB }

constructor TMyClassB.Create;
begin
  Records := @RECS;
end;

// ---------- Formular-Unit ---------

procedure TForm1.Button1Click(Sender: TObject);
var
  LClassA: TMyClassA;
begin
  LClassA := TMyClassA.Create;
  try
    ShowMessage(LClassA[1].A.ToString);
  finally
    FreeAndNil(LClassA);
  end;
end;

end.
Ich vermute mal, es kommt daher, dass ich hier TMyClass.FRecords als Zeiger auf ein dynamisches Array definiere und quasi als Platzhalter für ein statisches Array missbrauche.

In der Praxis ist die Array-Initialisierung um einiges komplexer. Bisher habe ich das über eine Klasse statt Record, ein TObjectList<TMyClass>, eine Schleife, ein case-Schleifenzähler-of und eine lange Codesequenz (ca. 670 Zeilen) gelöst. Mit den statischen Array-Initialisierungen im Klassenkopf dampft das Ganze auf 55 Zeilen ein, sollte weniger Laufzeit benötigen und ist sogar noch übersichtlicher.

Jetzt müsste nur noch die Runtime mitspielen ;-)

Grüße
Cody

jaenicke 26. Aug 2019 10:09

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Ein einfacher Trick um die unterschiedlichen Arraytypen anzugleichen ist die Deklaration so zu ändern:
Delphi-Quellcode:
type
  TMyRecords = array[0..100] of TMyRecord;

TiGü 26. Aug 2019 10:10

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Delphi-Quellcode:
unit Codehunter.View;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  type
  TMyRecord = record
    A: Integer;
    B: Integer;
    C: Byte;
  end;

  TMyRecords = array of TMyRecord;
  PMyRecords = ^TMyRecords;

  TMyClass = class
  private
    FRecords: TMyRecords;
  protected
    function GetRecord(const AIndex: Integer): TMyRecord;
  public
    property Records[const AIndex: Integer]: TMyRecord read GetRecord; default;
  end;

  TMyClassA = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6)
    );
  public
    constructor Create;
  end;

  TMyClassB = class(TMyClass)
  strict private const
    RECS: array[0..2] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6),
      (A: 7; B: 8; C: 9)
    );
  public
    constructor Create;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyClass }

function TMyClass.GetRecord(const AIndex: Integer): TMyRecord;
begin
  Result := FRecords[AIndex];
end;

{ TMyClassA }

constructor TMyClassA.Create;
var
  I: Integer;
begin
  System.SetLength(FRecords, Length(RECS));
  for I := System.Low(RECS) to System.High(RECS) do
    System.Move(RECS[I], FRecords[I], SizeOf(RECS[I]));
end;

{ TMyClassB }

constructor TMyClassB.Create;
var
  I: Integer;
begin
  System.SetLength(FRecords, Length(RECS));
  for I := System.Low(RECS) to System.High(RECS) do
    System.Move(RECS[I], FRecords[I], SizeOf(RECS[I]));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LClassA: TMyClassA;
begin
  LClassA := TMyClassA.Create;
  try
    ShowMessage(LClassA[1].A.ToString);
  finally
    LClassA.Free;
  end;
end;

end.
Aber nur wenn du versprichst, dass TMyRecord keine referenzgezählten Typen verwendet.
Okay sind ordinale Typen (Byte, Integer, Cardinal, Int64). Pfui alles andere (string, Interface, dynamisches Array).

Neutral General 26. Aug 2019 10:12

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Deklarier TMyRecords mal so:

Delphi-Quellcode:
TMyRecords = Array[0..0] of TMyRecord;

Codehunter 26. Aug 2019 10:22

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Zitat:

Zitat von TiGü (Beitrag 1443277)
Delphi-Quellcode:
{ TMyClassB }

constructor TMyClassA.Create;
var
  I: Integer;
begin
  System.SetLength(FRecords, Length(RECS));
  for I := System.Low(RECS) to System.High(RECS) do
    System.Move(RECS[I], FRecords[I], SizeOf(RECS[I]));
end;

{ TMyClassB }

constructor TMyClassB.Create;
var
  I: Integer;
begin
  System.SetLength(FRecords, Length(RECS));
  for I := System.Low(RECS) to System.High(RECS) do
    System.Move(RECS[I], FRecords[I], SizeOf(RECS[I]));
end;

Genau DAS wollte ich eigentlich vermeiden. Erstens weils wieder eine Schleife wäre, zweitens weil ich dann Copy&Paste-Programmierung hätte in jeder Ableitung von TMyClass.

Zitat:

Zitat von Neutral General (Beitrag 1443279)
Deklarier TMyRecords mal so:

Delphi-Quellcode:
TMyRecords = Array[0..0] of TMyRecord;

Zitat:

Zitat von jaenicke (Beitrag 1443276)
Ein einfacher Trick um die unterschiedlichen Arraytypen anzugleichen ist die Deklaration so zu ändern:
Delphi-Quellcode:
type
  TMyRecords = array[0..100] of TMyRecord;

In beiden Fällen erhalte ich nun Kompilerfehler:
Delphi-Quellcode:
  TMyClassB = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6),
      (A: 7; B: 8; C: 9)
    ); //<-- "Anzahl der Elemente (3) weicht von der Deklaration (2) ab"

Uwe Raabe 26. Aug 2019 10:23

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Zitat:

Zitat von Codehunter (Beitrag 1443273)
Ich vermute mal, es kommt daher, dass ich hier TMyClass.FRecords als Zeiger auf ein dynamisches Array definiere und quasi als Platzhalter für ein statisches Array missbrauche.

Da hast du recht: das kann so nicht gehen! Ein dynamisches Array ist ein Zeiger auf das Array, das noch mit ein paar Zusatzinformationen garniert ist, die ein statisches Array nicht hat.

Eventuell hilft dieser Ansatz bei deinem Problem:
Delphi-Quellcode:
unit Unit455;

interface

type
  TMyRecord = record
    A: Integer;
    B: Integer;
    C: Byte;
  end;

  TMyRecords = TArray<TMyRecord>;

type
  TMyClass = class
  private
    FRecords: TMyRecords;
  protected
    function GetRecord(const AIndex: Integer): TMyRecord;
    procedure InitRecords(const Source: array of TMyRecord);
  public
    property Records[const AIndex: Integer]: TMyRecord read GetRecord; default;
  end;

  TMyClassA = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6)
    );
  public
    constructor Create;
  end;

  TMyClassB = class(TMyClass)
  strict private const
    RECS: array[0..2] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6),
      (A: 7; B: 8; C: 9)
    );
  public
    constructor Create;
  end;

implementation

uses
  System.Generics.Collections;

{ TMyClass }

function TMyClass.GetRecord(const AIndex: Integer): TMyRecord;
begin
  Result := FRecords[AIndex];
end;

procedure TMyClass.InitRecords(const Source: array of TMyRecord);
begin
  SetLength(FRecords, Length(Source));
  TArray.Copy<TMyRecord>(Source, FRecords, Length(Source));
end;

{ TMyClassA }

constructor TMyClassA.Create;
begin
  InitRecords(Recs);
end;

{ TMyClassB }

constructor TMyClassB.Create;
begin
  InitRecords(Recs);
end;

end.

Codehunter 26. Aug 2019 10:39

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
@Uwe: Perfekt! So gehts. An generische Arrays hatte ich auch schon gedacht, bekam dann aber auch da nicht die generischen mit den statischen übereinander. Dass es eine statische Copy-Methode gibt war mir gar nicht bewusst. So müsste das doch eigentlich sogar dann funktionieren, wenn im Record ein String-Element vorkommt, oder?

@TiGü und Neutral General: Der Compilerfehler mit der Elementanzahl war eigene Blödheit, ich hatte (natürlich) auch per Copy&Paste vergessen, die Elementanzahl zu ändern. Aber Uwes Lösung passt am besten, weil ich dann die Objektorientierung am besten einhalten kann.

p80286 26. Aug 2019 10:40

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Versuch es mal so:
Delphi-Quellcode:
 TMyRecords = array of TMyRecord;
  PMyRecords = ^TMyRecords[0];
Geht natürlich schief wenn nichts im Array ist.

Gruß
K-H

gubbe 26. Aug 2019 10:42

AW: Zugriffsverletzung bei Zeiger auf statisches Array
 
Warum nicht klassisch objektorientiert, ganz ohne hässliche Pointer?

Code:

unit Unit1;

interface

type
  TMyRecord = record
    A: Integer;
    B: Integer;
    C: Byte;
  end;

  TMyClass = class
  private
  protected
    function GetRecord(const AIndex: Integer): TMyRecord; virtual; abstract;
  public
    property Records[const AIndex: Integer]: TMyRecord read GetRecord; default;
  end;

  TMyClassA = class(TMyClass)
  strict private const
    RECS: array[0..1] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6)
    );
  protected
    function GetRecord(const AIndex: Integer): TMyRecord; override;
  public
  end;

  TMyClassB = class(TMyClass)
  strict private const
    RECS: array[0..2] of TMyRecord = (
      (A: 1; B: 2; C: 3),
      (A: 4; B: 5; C: 6),
      (A: 7; B: 8; C: 9)
    );
  protected
    function GetRecord(const AIndex: Integer): TMyRecord; override;
  end;

implementation

function TMyClassA.GetRecord(const AIndex: Integer): TMyRecord;
begin
  result := Recs[AIndex];
end;

function TMyClassB.GetRecord(const AIndex: Integer): TMyRecord;
begin
  result := Recs[AIndex];
end;

end.
Und so kannst Du auch die Übergabeparameter noch prüfen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:22 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