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.
As you move through the list and follow each pointer, you can visit each house.
Street of houses (i.e. your blocks of data, 1Kb each) 10k 11k 12k 13k 14K +------------+------------+------------+------------+------------+ |Apple |Pear | |Banana |Orange | | | | | | | | H1 | H2 | | H3 | H4 | +------------+------------+------------+------------+------------+ Your list of addresses (aka 4 byte pointers) is stored at memory address 100k var ptrlist: array[0..3] of pointer; assuming the list has been initialized with the correct addresses ptrlist 100k contains 10k ptrlist 100k+4 contains 11k ptrlist 100k+8 contains 13k ptrlist 100k+12 contains 14k for var ix := 0 to Length(ptrlist) - 1 do begin here, ptrlist[ix] = 10k,11k,13k,14k, and ptrlist[ix]^ = whatever value that is stored in the respective house that the pointer addresses f.x. ptrlist contains 11k (and that value is stored at 100k+4), and ptrlist^ points to 'Pear', i.e. what is stored from address 11k
Why the empty house?
To exemplify that your list of pointers may be a consecutive array or linked list, but the data each pointer points to does not necessarily need to be consecutive.
Now, if you address ptrlist – you are out of bounds on the pointer list, and if you are so “lucky” that the address @ptrlist (which is 100k+16) is not inaccesible, then ptrlist^ will point you to whatever random value that pointer contains,, and most likely give you an access violation, or for sure – point you to data you are not meant to visit.
@ = Get the address of the reference.
@Variable gives you the pointer memory address of the content of Variable
var v: integer; p: pointer; begin v := 5; p := @v; // @ finds the address of v
p now contains the memory address of v
^ = Look at that address
p^ = v = 5
The ^ also is used when declaring a type pointer – i.e. a type safe reference to the memory we are going to access. A typed pointer is just the same size as the basic pointer type, since it also is just a memory reference, but we now also know the size of what the pointer refers to.
Use of pointers in APIs
APIs come in many flavours, and one frequently used model for Windows APIs is that you allocate a structure and then pass the address of that structure to the API. The API call will then fill the structure with data which you can use.
type TData = Array of char; PData = ^TData; // Defines a typed pointer TQueryNameData = record Length: Integer; Name: PData; end;
So, what is the size of TQueryNameData?
SizeOf( TQueryNameData) = SizeOf(Length) + SizeOf(Name) = 4 + 4 = 8
Name: PData is only a pointer and hence needs to be allocated separately.
Hence in this example, we manage the memory ourselves
const MaxLen = 100; var GetName: TQueryNameData; begin // first we prepare a memory location to hold the data // This API allows us to define a max size that it will copy // so that we can avoid a buffer overrun GetName.Length := MaxLen; GetName.Name := AllocMem(GetName.Length * SizeOf(char)); Then we pass the address of our variable to the API if QueryNameAPI(@GetName) then begin // GetName.Name^ should now contain data filled from the API // NB: Remember to call FreeMem for each AllocMem
Another API model is to have a Open/Process/Close use pattern, i.e. you first query the API to get a handle and f.x. a list of pointers. These pointers will then be accessible until you call the related closing API. In these cases, the memory is often managed by the API, and if you want to keep the values, you have to copy them during the process phase.
type TWho = record Name: PAnsiString; Address: PAnsiString; end; TWhoList = Array of TWho; PWhoList = ^TWhoList; TQueryWhoListData = record Count: Integer; Who: PWhoList; end; var Names: TQueryWhoListData; begin var h: handle := AcquireWhoList(@Names); if h > 0 // The API gave us a list then try for var ix := 0 to Names.Count - 1 do Writeln(Names.Who^[ix].Name^, ' ', Names.Who^[ix].Address^); // or copy the data to your own structure finally ReleaseWhoList(h); // Let go of the API end;
Look at Names.Who^[ix].Name^:
The first ^ points to the address where the array of TWho records is located.
The second ^ points to the address where the Name Ansistring is located.
The same goes for Names.Who^[ix].Address^ as well.
In modern Delphi you can actually do away with the ptr^ notation for typed pointers, and simply type ptr, and the compiler understands that it is what you refer to that you want, and not the address of what you refer to.
This means that for typed pointers, Names.Who^[ix].Address^
can be written as Names.Who[ix].Address instead.
Personally I sometimes like to use the old-school notation for clarity.
So, why can’t you keep referencing the data you received after letting go of the API? The simple answer is that you no longer can trust the referred memory to be available and unchanged. Typically, the API opening may allocate a block of memory that it fills and passes you, and it will release that block after you close the API.
For more in-depth information, please visit http://rvelthuis.de/articles/articles-pointers.html – which delves deeper into the topic of pointers.