Demystifying pointers in Delphi

Having a general understanding of how memory and addressing works, helps you understand pointers in Delphi (and C/C++). Learning assembler also gives you that knowledge, but there are simpler ways to think about it.

A pointer is a memory location that contains the address to the actual data you want.

Think of a street with houses. Make a list of the houses and their addresses.
This is a list of pointers. Each pointer in the list leads to the actual house it refers to.

Continue reading “Demystifying pointers in Delphi”

Weird code snippet #2: Generic Double Linked List

They say Generics and pointers don’t mix.

type
PMyThing = ^TMyThing // [DCC Error] E2508 type parameters not allowed on this type
TMyThing = record
Thing: T;
end;

Ok, they don’t. But there is a loophole!

type
TMyThing = record
Thing: T;
NextThing: ^TMyThing;
end;

Why this is allowed, I don’t know. Just like I don’t really understand why the first one is forbidden. There is probably some good explanation for it.

Still – it can be fun breaking the rules!

unit GenericDoubleLinkedList;

interface
uses
Classes, Generics.Defaults;

type
TLinkVisitor = reference to procedure(const Item: T);

TDoubleLinked = record
Value: T;
PrevLink: ^TDoubleLinked; // Hey, it compiles!
NextLink: ^TDoubleLinked;
constructor Create(aValue:T);
function Add(aValue:T): TDoubleLinked;
function HasNext:Boolean;
function Next: TDoubleLinked;
function HasPrev:Boolean;
function Prev: TDoubleLinked;
function First: TDoubleLinked;
function Last: TDoubleLinked;
procedure ForEach(const Proc: TLinkVisitor);
end;

procedure Test(const Log:TStrings);

implementation

{ TDoubleLinked }

constructor TDoubleLinked.Create(aValue: T);
begin
Value := aValue;
NextLink := nil;
PrevLink := nil;
end;

function TDoubleLinked.Add(aValue: T): TDoubleLinked;
var
p: ^TDoubleLinked; // But this one is not assignment compatible
begin
p := AllocMem(SizeOf(TDoubleLinked)); // Make space
p^ := Self; // Copy current value to allocated block
Value := aValue; // Set self to new value
p.NextLink := @Self;
if Assigned(p.PrevLink) // Fix up previous nextlink
then Pointer(p.PrevLink.NextLink) := Pointer(p);
Pointer(PrevLink) := Pointer(p); // Point back to old value
Result := Self;
end;

function TDoubleLinked.HasPrev: Boolean;
begin
Result := PrevLink nil;
end;

function TDoubleLinked.Prev: TDoubleLinked;
begin
Result := TDoubleLinked(PrevLink^)
end;

function TDoubleLinked.HasNext: Boolean;
begin
Result := NextLink nil;
end;

function TDoubleLinked.Next: TDoubleLinked;
begin
Result := TDoubleLinked(NextLink^)
end;

function TDoubleLinked.First: TDoubleLinked;
begin
Result := Self;
while Result.HasPrev
do Result := Result.Prev;
end;

function TDoubleLinked.Last: TDoubleLinked;
begin
Result := Self;
while Result.HasNext
do Result := Result.Next;
end;

procedure TDoubleLinked.ForEach(const Proc: TLinkVisitor);
var
Node: TDoubleLinked;
begin
Node := First;
Proc(Node.Value);
while Node.HasNext
do begin
Node := Node.Next;
Proc(Node.Value);
end;
end;

procedure Test(const Log:TStrings);
var
List, Node : TDoubleLinked;
begin
List.Create('One');
List.Add('Two');
List.Add('Three');
Node := List; // Bad idea
List.Add('Four');
Node.Add('ThreeAndAHalf');

List.ForEach(
procedure(const Value:String)
begin
Log.Add('List: ' + Value)
end);

Node.ForEach(
procedure(const Value:String)
begin
Log.Add('Node: ' + Value)
end);
end;

end.

The problem is that “List” is not a pointer, but the tail item of the list. Hence, a Delete procedure needs to take this into consideration.

Even worse, if you add a second Node variable to point to something in the list, that reference will not be fixed up after adding ‘Four’, and hence it will take the tail place of the list – for both references, effectively forgetting the ‘Four’ item.

So, although this was somewhat entertaining, a mix of Generics and pointers probably isn’t something we should make use of.

Exercise for the reader: Implement the TDoubleLinked.Delete; procedure.

End question: Why are we not allowed to declare pointers to generic types?