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
};