Demystifying pointers in Delphi by Lars Fosdal

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
Demystifying pointers in Delphi
Lars Fosdal - 31/Oct/2019
[SHOWTOGROUPS=4,20]

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[0] 100k contains 10k
ptrlist[1] 100k+4 contains 11k
ptrlist[2] 100k+8 contains 13k
ptrlist[3] 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[1] contains 11k (and that value is stored at 100k+4),
  • and ptrlist[1]^ 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[4] – you are out of bounds on the pointer list, and if you are so “lucky” that the address @ptrlist[4] (which is 100k+16) is not inaccesible, then ptrlist[4]^ 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.

Pointer Operators
  • @ = 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 then // The API gave us a list
  begin
    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;
  end;
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.

[/SHOWTOGROUPS]
 
Последнее редактирование: