Dynamische Array und Pointer Vermischung

Für Fehler in Lazarus, um diese von anderen verifizieren zu lassen.
Mathias
Beiträge: 7286
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Dynamische Array und Pointer Vermischung

Beitrag von Mathias »

Wieso wird dieser Code compiliert, ich weise direkt einen Pointer auf ein dynamisches Array zu ?
Ich habe dies entdeckt, weil ich das bei ball vergessen habe.

Code: Alles auswählen

type
  TdBodyID = Pointer;

  function CreateBalls: TdBodyID;
  begin
    Result := nil;
  end;

const
  COUNT = 3;
var
  ball: array of TdBodyID = nil;
begin
  ball := CreateBalls;  // Wieso geht dies
end.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

Warum denkst du, dass das nicht kompilieren sollte?
https://wiki.freepascal.org/Dynamic_array
https://wiki.freepascal.org/Nil

Mathias
Beiträge: 7286
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Dynamische Array und Pointer Vermischung

Beitrag von Mathias »

Warum denkst du, dass das nicht kompilieren sollte?
Weil es überhaupt keinen Sinn macht, bei nil ist es was anderes.

Dafür habe ich bei deinem Link etwas sehr spannendes entdeckt, ich habe gar nicht gewusst, das sowas geht:

Code: Alles auswählen

type
  truths = array of boolean;
var
  l: truths;
  i: integer;

  function getBoolean: boolean;
  begin
    Result := boolean(Random(2));
  end;

begin
  Randomize;
  l := truths.create(False, True, True);
  for i := 0 to Length(l) - 1 do begin
    WriteLn(l[i]);
  end;
  WriteLn();
  l := truths.create(getBoolean, getBoolean, getBoolean, getBoolean, getBoolean, getBoolean, getBoolean);
  for i := 0 to Length(l) - 1 do begin
    WriteLn(l[i]);
  end;
end. 
Gibt es da noch andere interessante Sachen ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

Mathias hat geschrieben: Sa 2. Mai 2026, 19:26 bei nil ist es was anderes.
Warum? Nil ist auch nur ein Pointer.
Kann man auch so schreiben:

Code: Alles auswählen

  ball:=Pointer(0);
  if ball=nil then writeln('nil');

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7293
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Dynamische Array und Pointer Vermischung

Beitrag von af0815 »

theo hat geschrieben: So 3. Mai 2026, 10:08
Mathias hat geschrieben: Sa 2. Mai 2026, 19:26 bei nil ist es was anderes.
Warum? Nil ist auch nur ein Pointer.
Kann man auch so schreiben:

Code: Alles auswählen

  ball:=Pointer(0);
  if ball=nil then writeln('nil');
ich würde mit der Annahme nil ist gleich Pointer(0) nicht undbedingt arbeiten. Per Definition (IMHO) sagt nil aus, "not in List" trifft aber keine Annahme, welcher Wert genau dafür verwendet wird. Genaugenommen kann theoretisch jeder Wert als Flag genommen werden. Und es gibt hin und wieder Diskussionen, ob der Wert für nil nicht auf einen anderen Wert gelegt werden kann/sollte (für bessere Fehlersuche). Das es normalerweise Pointer(0) ist ist IMHO ein Implementationdetail vom FPC.

https://wiki.freepascal.org/Nil
https://www.gnu-pascal.org/gpc/nil.html
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

af0815 hat geschrieben: So 3. Mai 2026, 10:47 ich würde mit der Annahme nil ist gleich Pointer(0) nicht undbedingt arbeiten. Per Definition (IMHO) sagt nil aus, "not in List" trifft aber keine Annahme, welcher Wert genau dafür verwendet wird. Genaugenommen kann theoretisch jeder Wert als Flag genommen werden. Und es gibt hin und wieder Diskussionen, ob der Wert für nil nicht auf einen anderen Wert gelegt werden kann/sollte (für bessere Fehlersuche). Das es normalerweise Pointer(0) ist ist IMHO ein Implementationdetail vom FPC.
Ja, das war aber nicht mein Punkt.
Mathias nimmt einen grundsätzlichen Unterschied zwischen nil (auch nihil, lat: "nichts") und Pointer an.
Den sehe ich nicht so. Nil ist einfach ein spezieller Pointer.

Mathias
Beiträge: 7286
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Dynamische Array und Pointer Vermischung

Beitrag von Mathias »

Aber müsste FPC in meinem Beispiel nicht mindestens einen cast verlangen ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

Mathias hat geschrieben: So 3. Mai 2026, 11:43 Aber müsste FPC in meinem Beispiel nicht mindestens einen cast verlangen ?
Ja, ist schon etwas merkwürdig.
Vielleicht kann PascalDragon das erklären.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7293
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Dynamische Array und Pointer Vermischung

Beitrag von af0815 »

theo hat geschrieben: So 3. Mai 2026, 11:54
Mathias hat geschrieben: So 3. Mai 2026, 11:43 Aber müsste FPC in meinem Beispiel nicht mindestens einen cast verlangen ?
Ja, ist schon etwas merkwürdig.
nil ist ein reserviertes Wort. So gesehen ist es typenlos und braucht deswegen keinen Cast.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

af0815 hat geschrieben: So 3. Mai 2026, 12:17 nil ist ein reserviertes Wort. So gesehen ist es typenlos und braucht deswegen keinen Cast.
Er weist ja der Variablen "ball" (array of TdBodyID) nicht direkt Nil zu sondern das Resultat der Funktion "CreateBalls" vom Typ TdBodyID.
Das kompiliert, aber ich finde es auch etwas verwunderlich, dass sich hier gar keine Typprüfung mehr meldet.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 7293
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Dynamische Array und Pointer Vermischung

Beitrag von af0815 »

CreateBalls hat den richtigen Typ auch das Ergebnis von createBalls passt. Und Result:= nil ist eine korrekte Zuweisung. Warum sollte der Compiler hier was meckern ?

Nil ist schon eine Spezialität. :mrgreen:

Noch ärger ist oft NULL in Datenbanken, das macht Freude :mrgreen:

Edit: Aufgelöst

Code: Alles auswählen

type
  function CreateBalls: Pointer;
  begin
    Result := nil;
  end;

var
  ball: array of Pointer = nil;
begin
  ball := CreateBalls;  // Wieso geht dies
end.
Hier arbeitest du nur mit Pointer und array of Pointer. Und ein array kann ja auch mit einem Pointer beschrieben werden, auch wenn es ein Array of pointer ist.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Warf
Beiträge: 2292
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Dynamische Array und Pointer Vermischung

Beitrag von Warf »

Mathias hat geschrieben: Sa 2. Mai 2026, 15:14 Wieso wird dieser Code compiliert, ich weise direkt einen Pointer auf ein dynamisches Array zu ?
Weil Pascal ein nicht sonderlich striktes Typsystem hat. Insbesondere bei Pointern macht der FPC viele (vielleicht auch ungewollte) Konvertierungen implizit
theo hat geschrieben: So 3. Mai 2026, 11:39 Ja, das war aber nicht mein Punkt.
Mathias nimmt einen grundsätzlichen Unterschied zwischen nil (auch nihil, lat: "nichts") und Pointer an.
Den sehe ich nicht so. Nil ist einfach ein spezieller Pointer.
Nil ist tatsächlich kein Pointer, sondern nil ist ein Ausdruck dessen Typ sich aus dem Kontext ergibt indem es steht.

Beispiel:

Code: Alles auswählen

var
  pi: PInteger;
  pd: PDouble;
  p: Pointer;
  o: TObject;
begin
  pi:=nil; // nil ist hier vom typ PInteger
  pd:=nil; // nil ist hier vom Typ PDouble
  pi:=pd; // Error denn pd ist typ PDouble und damit inkompatibel zu PInteger (obwohl beides nil)
  p:=nil; // nil ist hier vom typ Pointer
  o:=nil; // nil ist hier vom typ TObject
  o:=p; // Error den p ist vom typ Pointer und damit inkompatibel zu TObject (obwohl beides nil)
end.
Hier nimmt nil ganz verschiedene typen an zwischen denen es keine implizite Convertierung geben dürfte.

Damit ist Nil übrigens einzigartig. Sonst hat in Pascal jeder Ausdruck einen festen Typen welcher nach den existierenden oder nutzerdefinierten Überladungen impliziet konvertieren lässt.

Benutzeravatar
theo
Beiträge: 11304
Registriert: Mo 11. Sep 2006, 19:01

Re: Dynamische Array und Pointer Vermischung

Beitrag von theo »

Warf hat geschrieben: So 3. Mai 2026, 17:42 Nil ist tatsächlich kein Pointer, sondern nil ist ein Ausdruck dessen Typ sich aus dem Kontext ergibt indem es steht.

Beispiel:

Code: Alles auswählen

var
  pi: PInteger;
  pd: PDouble;
  p: Pointer;
  o: TObject;
Hier nimmt nil ganz verschiedene typen an zwischen denen es keine implizite Convertierung geben dürfte.
Was verstehst du unter Pointer?
Für mich sind das alles Zeiger/Pointer, halt teilweise typisierte.
Die zeigen alle auf eine Speicherstelle. Welche Daten dort zu erwarten sind, ist erst einmal zweitrangig.
Nil sagt ja, dass der Zeiger auf nichts zeigt und ungültig ist, dabei spielt der Typ im Grunde keine Rolle.

Warf
Beiträge: 2292
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Dynamische Array und Pointer Vermischung

Beitrag von Warf »

Wenn ich sage Pointer, meine ich Pointer Typen, also den typen Pointer, sowie alle Typen die mit ^ definiert werden. Klasseninstanzen (TObject), dynamische Strings (AnsiString) und dynamische Arrays (array of ...) sind zwar technisch auch Speicheraddressen, sind aber Semantisch keine Zeiger sondern eigene Typen im Typsystem.

Ein besonders gutes Beispiel hierfür sind Methoden:

Code: Alles auswählen

var
  proc: procedure;
  method: procedure of Object;
  nested: procedure is nested;
begin
  proc:=nil;
  method:=nil;
  nested:=nil;
  WriteLn(SizeOf(Pointer)); // 4 auf 32 bit Windows
  WriteLn(SizeOf(proc)); // 4 auf 32 bit Windows
  WriteLn(SizeOf(method)); // 8 auf 32 bit Windows
  WriteLn(SizeOf(nested)); // 8 auf 32 bit Windows
end.
Proc ist so mit 4 bytes genauso groß wie ein Pointer, das macht auch Sinn denn intern sind beides nur Speicheraddressen. Method und Nested sind mit 8 Bytes jeweils doppelt so groß. Der Grund dafür ist das es sich hierbei nicht um eine Speicheraddresse handelt, sondern um einen Record, TMethod um genau zu sein. Bei Methoden (procedure of object), handelt es sich hierbei um 2 Pointer, zum einen eine Codepointer zum anderen einen zum Objekt das als Self Parameter übergeben werden soll. Bei Nested Funktionen sind es auch 2 Pointer, zum einen der Codepointer, zum anderen der Kontext über den auf die Variablen des überliegenden Stackframes zugegriffen werden kann.

Hierbei handelt es sich also um drei technisch grundsätzlich unterschiedlich umgesetzte Typen, folgen aber alle einer ähnlichen Semantik sodass sie sich normalerweise transparent austauschen lassen. Pascal ist eine High-Level Sprache, d.h. als Entwickler muss man Eigentlich nicht wissen wie das unter der haube funktioniert, man sollte Anhand der Sprachspezifikation alles umsetzen können, egal ob Procedures, TObject, Pointer, Arrays, etc. intern als Speicheraddresse, als komplexer Datentyp oder als Feenstaub umgesetzt werden. Und auf Sprachebene sind TObject und Pointer zwei nicht wirklich verwandte Typen. Genauso wie procedure und Pointer nicht wirklich verwandt sind.

Und der Punkt hier ist, was man oben auch sehr gut sieht: Nil ist, entsprechend nach dem Kontext entweder ein Typisierter oder Untypisierter Pointer, ein Array oder eine Procedure, es kann entweder eine Speicheraddresse sein oder sogar, wie im falle von Methoden oder Nested Procedures, ein Record.

Wobei ich zugeben muss das Pascal das ganze nicht so gut trennt. Also die Trennung zwischen Klasseninstanzen und Pointern ist ganz gut, hier muss man immer Explizit Casten um umzuwandeln, aber wie im Beispiel vom Anfang des Threads, ist diese Trennung zwischen Dynamischen Arrays und Pointern nicht so streng, und das obwohl hier die Referenzzählung ziemlich viele Sachen kaputt machen kann. Die Trennung zwischen Strings und Pointern ist allerdings wiederum recht streng. Auch die Trennung zwischen Typisierten Pointern ist streng, aber dann ist der Switch TypedAddress by default ausgestellt, womit der Addressoperator immer eine Untypisierten Pointer holt was solche Späße erlaubt:

Code: Alles auswählen

var
  c; Char;
  pi: PInteger;
begin
  pi:=@c; // Ohne {$TypedAddress On} ist das hier kein Fehler
end;
Daran merkt man das die Sprache historisch gewachsen ist und verschiedene Leute an Verschiednen Stellen entscheidungen getroffen haben die in Summe sehr inkonsistent sind.


Aber um ehrlich zu sein finde ich das nil keinen Typen hat sehr unintuitiv, es ist der einzige Ausdruck bei dem der Typ von dem Kontext abhängig ist was vielleicht in Original Pascal bzw. zu Zeiten von ISO Pascal noch Sinnig war, da es dort nur klassiche Pointer Typen gab, aber in modernem ObjectPascal ist das ein riesen Wildwuchs. Da finde ich den Ansatz von Sprachen wie C++ aber auch Swift, oder Typescript deutlich besser bei denen das equivalent (nullptr, nil und undefined) ihren eigenen Typen haben der dann implizit auf die entsprechenden Werte gecastet werden kann. Das erlaubt auch statisches Überladen von Funktionen und Operatoren um eigene neue Typen zu schreiben, z.B. in C++:

Code: Alles auswählen

class MyListClass {
...
  MyListClass &operator =(nullptr_t) { clear(); } // Erlaubt es "mylist = nullptr;" zu schreiben um die Liste zu clearen
};

Socke
Lazarusforum e. V.
Beiträge: 3188
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Dynamische Array und Pointer Vermischung

Beitrag von Socke »

Gerade habe ich keinen FPC zur Hand, habt ihr mal Versucht nil zu Typecasten?

Code: Alles auswählen

var
  p: PInteger;
begin
  p := PInteger(nil);
  p := PByte(nil);
end;
Wenn es dabei zu Compiler-Fehlern kommt, verhält sich nil wie andere konstante Ausdrücke. Beispielsweise ermittelt der Compiler für Ganzzahlen einen passenden Typen; man kann aber auch explizit einen Typen angeben.

Code: Alles auswählen

const
  i1 = 12; // implizite Typisierung
  i2 = Word(12); // explizite Typangabe
  i3 = Longint(4294967295); // explizite Typangabe
Mathias hat geschrieben: Sa 2. Mai 2026, 19:26 Gibt es da noch andere interessante Sachen ?
Du kannst Pointer auch mit Array-Syntax verwenden:

Code: Alles auswählen

var
  p: PInteger;
begin
  p[20] := 15;
end;
Basierend auf dem Basistypen errechnet der Compiler dann automatisch die Speicheradresse. Im Gegensatz zu Arrays fällt dann aber die Speicherveraltung, Größeninformationen und ggf. RangeChecking weg.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Antworten