Home > Articles

0672320606

  • Print
  • + Share This

Pointers

Pascal pointers are just like C or C++ pointers: They are the key to what makes the language so powerful, but they can also cause you considerable pain.

I'll start by showing you what the untamed pointer type looks like in Object Pascal. Most people won't want to use pointers this way, but I think it helps to start by seeing the low-level usage. Then I'll show you the somewhat more civilized way pointers most often appear in Kylix programs. This is the undomesticated version:

procedure TForm1.Button1Click(Sender: TObject);
var
  P: Pointer;
  S, Temp: String;
begin
  S := `Sam';                // Set S = `Sam'
  GetMem(P, 10);             // Allocate 10 bytes
  FillChar(P^, 10, 0);       // Zero out all 10 bytes
  Move(S[1], P^, Length(S)); // Move `Sam' into P^
  SetLength(Temp, 4);        // Make Temp four bytes long
  Move(P^, Temp[1], 1);      // Move S from P to temp
  Inc(PChar(P));             // Make P point to am
  Move(P^, Temp[2], 1);      // Move a from P to temp
  Inc(PChar(P));             // Make P point to m
  Move(P^, Temp[3], 1);      // Move m from P to temp
  Temp[4] := #0;             // Set the null terminator for temp
  Dec(PChar(P), 2);          // Make P point at Sam
  FreeMem(P, 10);            // Free memory allocated for Sam
  Edit1.Text := Temp;
end;

This code is from a program named Pointers, which is in the Chap03 directory on your accompanying CD. The Button1Click method shown here does nothing practical other than show how to use pointer syntax in Object Pascal. In particular, the code creates a pointer of 10 bytes of memory. It zeroes out the bytes that the pointer addresses. Then it copies a string into the bytes address by the pointer. Finally, it accesses the contents of pointer, 1 byte at a time.

The Pascal pointer type is, logically enough, referred to as a pointer. Use the ^ operator operator>to address the memory associated with a pointer. The ^ operator performs more or less the same function in Pascal as the -> operator does in C and C++.

I have commented this code so heavily that you can probably best follow it by simply reading the code itself. Notice, in particular, the following calls:

  • The GetMem procedure allocates memory for a pointer. Pass the pointer in the first parameter and the number of bytes that you want to allocate in the second parameter. You will receive a copy of a fully initialized pointer to the amount of memory that you requested.

  • FillChar can be used to spray a single-character value into multiple contiguous bytes in a very efficient manner. Commonly, it is used to zero out the bytes in a contiguous chunk of memory. In this case, I tell FillChar to fill the 10 bytes pointed to by P with the value 0. This is the first character in the character set, not the number 0. The first parameter of the function specifies the memory that you want to fill, the second specifies the number of bytes of that memory that you want to address, and the third parameter is the character that you want to spray into those bytes. Needless to say, the routine is designed to be very fast.

  • The Move procedure moves n number of bytes from one place in memory to another place. In this case, I move the 3 bytes addressed by the String variable into the bytes address by the pointer. The first parameter of the Move procedure is the source memory, the second is the destination memory, and the third is the number of bytes to be moved.

  • Notice that I use the Inc and Dec procedures to iterate over particular bytes in the memory addressed by the pointer. This technique is known as pointer arithmetic, and it should be familiar to all C or C++ programmers. You will get a more detailed look at pointer arithmetic in Chapter 5, in the sections on the debugger. After the call to Move, the pointer points at the letters S, a, and m. The last 7 bytes addressed by the pointer are still zeroed out. If I call Inc(P) one time, then the first byte addressed by the pointer is no longer S, but a. In C or C++, you would achieve the same effect by writing P++.

  • The FreeMem procedure deallocates the memory assigned to the pointer P. Notice that I use Dec to unwind the memory addressed by the variable P back to the place initially allocated by the call to get GetMem. Failure to do this can yield undefined results.

The best way to understand these lines of code is to watch them very carefully in the debugger. Unfortunately, I have not yet talked to you about using the debugger. Furthermore, the parts of the debugger that you can use to really get a look at this code are relatively advanced features. As a result, I'm going to have to ask you to put this code aside for a time. Then, in Chapter 5, in the section "Using the Debugger," I will show you how to get a good look at what happens under the hood when this code is executed.

Working with Pointers to Objects

The type of pointer that I showed you in the last section is really an advanced technique, not one that you are likely to employ in many sections of your code. Pointers to objects, however, are ubiquitous in Object Pascal, and you must understand them if you want to work with Kylix.

NOTE

In this section and the next, I talk a good deal about how objects work in Kylix. I assume that all readers of this book already understand objects as well as polymorphism, inheritance, encapsulation, virtual methods, and related topics. My goal in this text is not to explain OOP in and of itself, but instead to introduce or reintroduce Pascal objects to experienced programmers from other languages such as C++, VB, and Java.

Like Strings, pointers have a rather checkered history in Object Pascal. For many years, a Pascal pointer was a Pascal pointer, and as long as you understood how memory is addressed by a computer, they were fairly simple to use. In recent years, however, the waters have been muddied to make some changes that make programming easier for certain types of developers.

For many years, the basic rule for pointers was that if you wanted to address the memory they referenced, you used pointer syntax. For instance, you wrote MyPointer^.SomeBlockOfMemory. Spoken out loud, this is usually rendered as, "MyPointer points to SomeBlockOfMemory." This syntax is easy for intermediate and advanced-level programmers to use. However, inexperienced programmers can find it disconcerting. To solve their feelings, the Pascal team decided to drop the ^ operator. The result is simple dot notation: MyPointer.SomeBlockOfMemory. When working with Pascal objects, you can now leave off the ^.

Note, in particular, that all CLX objects should be addressed using dot notation. No one writes TEdit^.Text; they all write TEdit.Text.

You and I know, however, that this dropping of the pointer syntax is a simple affectation. It is a euphemism. Underneath this thin veneer, the untamed beast sharpens its claws on the bark of exotic tropical trees, its lidded eyes never still, its body crouching as it waits for you to make a false move. At this point it will—well, I'm not allowed to talk about that kind of thing in a book intended for general audiences. But if you have ever used a Microsoft product or attended a Bill Gate's demo, or if you have had a glimpse of the dreaded blue screen of death, then I'm sure you know something of what can happen.

NOTE

When they dropped the pointer syntax for objects, the Delphi and Kylix engineers also decided to place a T rather than a P before the declarations for these objects. For instance, it is proper to write TEdit or TButton, but not PEdit or PButton. Normally, you would put a P rather than a T before a pointer type. However, the game is properly afoot, and to play along you should use a T rather than a P.

The biggest issue when you use pointers is to make sure that the pointer is allocated properly and, even more importantly, that it is disposed of properly. In Kylix, these chores are largely taken out of the users' hands by two mechanisms:

  1. In design mode, when you drop a component onto a form or onto another component, memory for that component is allocated automatically.

  2. When a form closes, it automatically disposes of the memory associated with all the components that it owns. In fact, when any component that owns another component is destroyed, that component frees the memory for all the components that it owns. (In the section "Arrays", you saw the Component array that belongs to a form. We used that array for our purposes, but its primary purpose is to allow the form to track the components that it owns and to dispose of them properly when the time comes.)

Now you can begin to understand what the designers of Pascal have done:

  • They've made Pascal allocate memory for you automatically most of the time when you're working in the Form Designer.

  • When the time comes, Pascal will automatically dispose of the components.

  • They've unburdened developers of the need to use the ^ syntax.

If you put these three pieces together, you can see that Delphi goes a long way toward entirely relieving you of the duty to think about pointers or to even know that pointers are being used.

All of that is well and good—except for one crucial fact: Object Pascal uses pointers everywhere! Every component that you drop on a form is a pointer to an object. In fact, it is not possible to create a static instance of an object in Pascal. All Pascal components must be created as pointers to components. Indeed, all Pascal objects must be instantiated as pointers!

Listing 3.5 shows how to explicitly create an instance of an object. This is information that you must understand for those times when you create objects on your own rather than by dropping them onto the Component Palette:

Listing 3.5  A Short Console Application Found on Disk as ObjectAllocate1.dpr

program ObjectAllocateOne;

{$APPTYPE CONSOLE}
uses SysUtils;

type
  TMyObject = class(TObject)
    procedure SayYourName;
  end;

{ MyObject }

procedure TMyObject.SayYourName;
begin
  WriteLn(ClassName);
end;

var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  MyObject.SayYourName;
  MyObject.Free;
end.

This program is a console application. You can create console applications by choosing File, New, Console Application from the menu system. Console applications are not run as part of X. Instead, they are run from the shell prompt and usually output only raw text.

NOTE

It is traditional to end console applications with the ReadLn statement while you are debugging them in the IDE. This ensures that the window that the program creates stays open until you press the Enter key. If you start Kylix from the shell prompt, there is no reason to use this mechanism. Instead, the text from your program will be seen at the shell prompt from which you started Kylix. (In fact, it might be the case in the first version of Kylix that this latter mechanism is the only way to see the output from your console applications.) Remember that when you start Kylix from the shell prompt, you should use the command startkylix.

The ObjectAllocateOne program declares a simple Object Pascal class named TMyObject. With rare exceptions, all Object Pascal classes should begin with T, which stands for "type." To declare the class, you write TMyObject = class(TObject), where TObject is the name of the parent class.

To create an instance of TMyObject, you write the following code:

var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  ... // Code omitted here

This code allocates memory for an object. Java and C++ programmers would achieve the same ends by writing MyObject = new TMyObject(). VB programmers would write Dim MyObject as TMyObject. But parallels with VB and Java are inappropriate because Pascal treats pointers the same way C++ does. If you allocate memory for an object, you must free that memory or risk crippling your program!

Here is how to free memory for a Pascal object:

  MyObject.Free;

This code destroys the memory associated with TMyObject. After you have made this call, you can no longer safely reference MyObject.

Pointers, Constructors, and Destructors

When you call Free on an object, the compiler also calls the destructor for your object, if there is one. The ObjectAllocateTwo program, shown in Listing 3.6, demonstrates how this works.

Listing 3.6  The ObjectAllocateTwo Program, Featuring a Constructor That Allocates Memory and a Destructor That Destroys That Same Memory

program ObjectAllocateTwo;
{$APPTYPE CONSOLE}
uses
  SysUtils;

type
  TMyObject = class(TObject)
  private
    FMyPointer: Pointer;
  protected
    constructor Create; virtual;
    destructor Destroy; override;
  public
    procedure SayYourName;
  end;

{ MyObject }

constructor TMyObject.Create;
begin
  inherited Create;
  GetMem(FMyPointer, 10);
end;

destructor TMyObject.Destroy;
begin
  FreeMem(FMyPointer, 10);
  inherited;
end;

procedure TMyObject.SayYourName;
begin
  WriteLn(ClassName);
end;

var
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create;
  MyObject.SayYourName;
  FreeAndNil(MyObject);
end.

Many programs contain pointers that need to be allocated and deallocated. When allocating memory for an object that will belong to another object, you should do one of two things:

  • Declare the pointer or object as a method-scoped variable, and then allocate and deallocate the memory for that object inside one single method. This is the way the variable P is treated in the Button1Click method from the earlier section of this chapter titled "Pointers." In that method, P belongs to the Button1Click method, and the Button1Click method therefore takes on the chore of both allocating and deallocating memory for that object.

  • The second method is shown in the AllocateObjectTwo program. In that program, the variable FMyPointer is declared as a field of TMyObject. (The name of all fields of Pascal objects should begin with F, which stands for "field.") The memory for FMyPointer is allocated in the object's constructor and deallocated in the object's destructor. If you do things in this way, you are unlikely to forget to either allocate or deallocate memory for your object. In particular, all you need do is see what pointers or objects are declared as fields of your object and then make sure that they are allocated in the constructor and deallocated in the destructor. If you have declared five fields for your object but only four of those fields are cleaned up in your destructor, you likely have a problem.

There is nothing in Object Pascal to keep you from allocating and deallocating memory for your objects and pointers wherever you please. These rules are merely guidelines. In some cases, it will not be possible to follow these guidelines, but you should heed them if you can.

virtual Methods and the override Directive

In the AllocateObjectTwo program, the directive virtual is placed after the constructor for TmyObject, and the directive override follows the destructor. The first time you declare a method that you want to be virtual, use the directive virtual. If you are overriding an already existing virtual method, use the directive override.

All Pascal objects are guaranteed to have a destructor named Destroy and a method named Free. The purpose of the Free method is to call the destructor. More particularly, the Free method ensures that the object is not already set to nil; if it is not, the method calls Destroy.

Pascal objects inherit the Free and Destroy methods from TObject. When it comes time for a component to deallocate the memory for all the components that it owns, it simply calls TObject.Free on all the objects in its Components array. Through the miracle of polymorphism, this single call ensures that the destructors for each object are called. The end result is that all the memory for these objects is destroyed. This mechanism will not work, however, unless you override the Destroy method in your programs whenever you need to deallocate the memory associated with any of the fields of your object.

There is no need to override Destroy in the AllocateObjectOne program because the instance of TMyObject in that program does not have any fields for which it allocates memory. This is not the case in the AllocateObjectTwo program, so it is necessary to create a destructor for that object. Note further that in the AllocateObjectTwo program, TMyObject is not owned by a component. As a result, you must specifically call its destructor, as shown in that program. If TMyObject were owned by a component, you would not need to explicitly call Free on that object because its owner would perform the task for you.

NOTE

Newcomers to Object Pascal likely will have further questions at this point about the difference between objects and components. In general, a component is simply an object that you can place on the Component Palette. Saying as much, however, raises as many questions as it answers. Part II is dedicated to components, and there you will learn about them in depth.

I've tried to cover most of the major subjects about pointers that I think can cause confusion. If you want to hear more about pointers to objects, don't worry—the subject will come up again in Part II, when the focus of the text becomes CLX.

  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.