Absolute (for) Beginners

I casually suggested the use of the “absolute” keyword in response to a question on the NZ DUG mailing list today.  I thought nothing of it but someone mentioned that it had been years since he’d seen anyone use it, so I thought maybe it was worth bringing to wider attention.


absolute is one of those language features that I’m sure will divide the community into those who think it’s a useful tool and those who think it should be avoided like a modern day goto.  I’m firmly in the former camp.

absolute allows you to direct the compiler to treat one or more variables as existing at the same memory location, effectively overlaying one (or more) on top of another.

The potential for making mistakes as a result of using such a technique is, I think, fairly clear, but in my view is not that different from the dangers of unchecked typecasting (or rather pre-checked hard casting). That is where a type check is performed and then once the type identity determined, then simply assumed for the following code.

It might help to explain what I mean by that with an example.

Imagine an event handler that responds to changes in a number of controls on the form where the precise identity of each control involved isn’t important, but the type of the control determines how the event responds. There being sufficient commonality in the event handler that maintaining separate events is undesirable. Let us say for example that all editable controls on the form should enforce uppercase text entry (please bear in mind that this is an example contrived to show the potential use of absolute – don’t nit-pick the example itself):

procedure TMyForm.EditableChange(Sender: TObject);
begin
  if Sender is TEdit then
  begin
    TEdit(Sender).Text := Uppercase(TEdit(Sender).Text);
  end
  else if Sender is TMemo then
  begin
     TMemo(Sender).Text := Uppercase(TMemo(Sender).Text);
  end
  else if (Sender is TComboBox) and (TComboBox(Sender).Style = csDropDown) then
  begin
     TComboBox(Sender).Text := Uppercase(TComboBox(Sender).Text);
  end;

  SetDirtyFlag(TRUE);
end;

Now, one way of “cleaning this up” might be:

procedure TMyForm.EditableChange(Sender: TObject);
var
  edit: TEdit;
  memo: TMemo;
  combo: TComboBox;
begin
  if Sender is TEdit then
  begin
    edit := TEdit(Sender);
    edit.Text := Uppercase(edit.Text);
  end
  else if Sender is TMemo then
  begin
    memo := TMemo(Sender);
    memo.Text := Uppercase(memo.Text);
  end
  else if (Sender is TComboBox) then
  begin
    combo := TComboBox(Sender);
    if (combo.Style = csDropDown) then
    begin
      combo.Text := Uppercase(combo.Text);
    end;
  end;

  SetDirtyFlag(TRUE);
end;

I don’t know about you, but I think the cure in this case is worse than the disease. So now let’s see how ‘absolute‘ makes this attempt to clean up far more successful:

procedure TMyForm.EditableChange(Sender: TObject);
var
  control: TObject;
  edit: TEdit absolute control;
  memo: TMemo absolute control;
  combo: TComboBox absolute control;
begin
  control := Sender;

  if Sender is TEdit then
  begin
    edit.Text := Uppercase(edit.Text);
  end
  else if Sender is TMemo then
  begin
    memo.Text := Uppercase(memo.Text);
  end
  else if (Sender is TComboBox) and (combo.Style = csDropDown) then
  begin
    combo.Text := Uppercase(combo.Text);
  end;

  SetDirtyFlag(TRUE);
end;

This is just as type-safe as each previous effort – type checking is being performed as required. The difference is that the type-casting is now effectively being performed in the declaration of the various variables themselves.

The absolute keyword tells the compiler to treat edit, memo and combo as simply different types of variable all located in the same location in memory as the control variable.

But more than that – to my mind it also acts as a clear indicator in the declaration of those variables themselves as to how those variables are used in the function or procedure body that follows, in a way that is not at all clear (without carefully reading the details of the code itself) in the previous example.

I like code that explains itself – it means I don’t have to write as much in the comments!

Now, in fact things can be made just as clear and a little less verbose, since as well as “overlaying” variables on top of one another, the absolute keyword can be used to “overlay” variables on parameters themselves, so in this case we can eliminate that rather distracting control variable:

procedure TMyForm.EditableChange(Sender: TObject);
var
  edit: TEdit absolute Sender;
  memo: TMemo absolute Sender;
  combo: TComboBox absolute Sender;
begin
  if Sender is TEdit then
  begin
    edit.Text := Uppercase(edit.Text);
  end
  else if Sender is TMemo then
  begin
    memo.Text := Uppercase(memo.Text);
  end
  else if (Sender is TComboBox) and (combo.Style = csDropDown) then
  begin
    combo.Text := Uppercase(combo.Text);
  end;

  SetDirtyFlag(TRUE);
end;

As I said, this is a not very nice and very contrived example, but I wanted to use something simple to introduce the idea before complicating things just a little.

Mutating the Identity of Schizophrenic Types

If you’ve ever had to work with resources in your applications – icons, bitmaps etc – you will no doubt be familiar with MAKEINTRESOURCE(). This little function converts a 16-bit resource ID into a value that can be used in place of a pointer to a resource name in some functions.

It also means that sometimes you will received a pointer to a resource name that is in fact not a pointer at all but one of these specially formatted resource ID values and needing to deal with it according to what the value contains – it could be a 16-bit integer or a pointer to a string.

This is potentially fiddly, but absolute makes it simple and, again, clearly states the intent of your code without requiring you to explicitly implement code to execute that intent:

function GetResourceIDAsString(const aID: PChar): String;
var
  id: Integer absolute aID;
begin
  if HiWord(aID) = 0 then
    result := IntToStr(id)
  else
    result := aID;
end;

Note: the test of the high 16-bits of the parameter passed could equally have been written as simply:

  if id <= MAXWORD then

Notice how this technique preserves the type safety of the supplied parameter (resource ID’s are treated as PChar) without involving any messy typecasts in the implementation itself. By contrast I think a common approach that you might encounter that didn’t use absolute would look something like:

function GetResourceIDAsString(const aID: PChar): String;
var
  dw: DWORD;
begin
  dw := DWORD(aID);

  if HiWord(dw) = 0 then
    result := IntToStr(LoWord(dw))
  else
    result := aID;
end;

Not a huge difference maybe (and a trivial, albeit representatively ‘real’, example in this case), but we have declared a variable and required a typecast for no good reason (in terms of the purpose of the function) than to allow us to work with the supplied value in a particular way. I suspect that in such a case the temptation would be to actually do away with the variable and repeatedly typecast:

function GetResourceIDAsString(const aID: PChar): String;
begin
  if HiWord(DWORD(aID)) = 0 then
    result := IntToStr(LoWord(DWORD(aID)))
  else
    result := aID;
end;

Which is shorter, no doubt but… well, perhaps some people like this sort of repetitive and heavily parenthesised code. I personally don’t.

To End At The Beginning

As I said at the outset, this all started when I responded to a question on the NZ DUG mailing list.  The question in, uh, question was about how to take advantage of the for in language construct introduced in Delphi 2007.  The case involved trying to use for in to iterate over the contents of an instance of existing class derived from TObjectList - i.e. no custom enumerator had yet been provided (and bear in mind that generics weren’t available as the Delphi version in question does not support them in Win32 code).

The question was asking why compilation errors resulted from code such as this (slightly simplified from the original question, the loop body in particular) and how to avoid them:

  procedure PrintFormats( aFormatList: TAddressFormatList );
  var
    addrFormat: TAddressFormat;
  begin
    for addrFormat in addrFormats do
      WriteLn( addrFormat.Description );
  end;

Since TAddressFormatList derived from TObjectList, this was failing with an incompatible types error, since the enumerator for TObjectList yields Pointer values, not TAddressFormat references.

The question was posed by someone who had seen someone demo the language feature by writing a for in loop to process the contents of a TStringList but who hadn’t perhaps fully appreciated the mechanism by which this language feature works and the infrastructure they needed to provide in order to make it work (which is of course provided by the VCL in the case of certain types already).

You could of course simply do as the TObjectList required, and enumerate using a pointer variable, type-casting each yielded value in the body of the loop:

  procedure PrintFormats( aFormatList: TAddressFormatList );
  var
    addrFormat: Pointer;
  begin
    for addrFormat in aFormatList do
      WriteLn( TAddressFormat(addrFormat).Description );
  end;

But with numerous uses of addrFormat in the loop this becomes messy. So you might introduce another, strongly typed variable, do the typecast once into that variable and then use that other variable in the body of the loop:

  procedure PrintFormats( aFormatList: TAddressFormatList );
  var
    addrFormatEnum: Pointer;
    addrFormat: TAddressFormat;
  begin
    for addrFormatEnum in aFormatList do
    begin
      addrFormat := TAddressFormat(addrFormatEnum);
      WriteLn(addrFormat.Description );
    end;
  end;

But we’ve already seen how absolute helps tidy this up even further and make the relationship between those variables absolutely [sic] crystal clear into the bargain:

  procedure PrintFormats( aFormatList: TAddressFormatList );
  var
    addrFormatEnum: Pointer;
    addrFormat: TAddressFormat absolute addrFormatEnum;
  begin
    for addrFormatEnum in aFormatList do
    begin
      WriteLn(addrFormat.Description );
    end;
  end;

That’s it.

As I say, expect there will be a number of comments to this pointing out the dangers of this technique, but I believe those dangers to be no greater, and in some ways somewhat less, than those of unchecked typecasts, which is what absolute essentially replaces. In it’s favour it helps eliminate “noise” and in my view makes the intent of variables clearer without requiring the reading of code to determine that usage.

If nothing else, it may be another tool in your toolbox.  Or perhaps a tool that you didn’t even realise you had at your disposal.

I also find it somewhat gratifying that this language feature, I think among one of the oldest to still survive in the language, can help make one of the newest language features more usable without having to spend time writing code that isn’t directly related to the business problem at hand (writing enumerators and working with enumerated lists without the benefit of generics or perhaps where a suitably typed enumerator simply hasn’t been provided).

Tags:

29 comments

  1. Xepol’s avatar

    It has been a long, long time since I used absolute. I believe I got out of the habit when it was suggested at tour event for Kylix that it may not work as effectively on that platform.

    I used to use it for item compare routines for .Sort in TList.

    I like the use in “Schizophrenic Types”. Thanks to the way certain winsock calls are imported, they are pchar when they should just be var or pointers – I’ll have to look and see if this would make that code more readable.

    Good post.

  2. Maarten’s avatar

    I really like your last example (for..in vs TObjectlist pointers) as I have been doing stuff exactly like that in a Delphi application recently, and the casting sort of spoiled the ‘cleanness’ the for..in loop was supposed to provide.
    I would have never thought of using absolute to work around that — to be honest, I had almost forgotten about that keyword… (“Or rather a tool that you perhaps didn’t realise you had at your disposal” is spot on!) It is a nice, clean and self-explanatory solution and from now on, definitely a new tool in my toolbox. Thanks!

  3. Jamie’s avatar

    Great article and well explained Jolyon! You learn something new every day and I think you just got in there early! :)

  4. Ken Knopfli’s avatar

    I vaguely recall the absolute keyword in Turbo Pascal for DOS.

    But I’m not sure I understand what’s happening on the “metal” level.

    How can memo and edit occupy the same memory? Aren’t they different sizes with different methods, etc?

    And how does this compare to variant types, which also allows different type views on the same memory location?

    1. Jolyon Smith’s avatar

      @Ken: “memo” and “edit” are merely labels for variables which *reference* objects of different types, but those references are both the exact same size (32-bit pointers).

      Usually if you declared two such variables the compile will allocate 32-bits for each one and if you wanted to put the value of one into the other you would have to do an assignment and, because Pascal is strongly typed, usually have to do a type-cast so that the compiler will allow it.

      “absolute” is shorthand for all of this shennanigans. it tells the compiler “this variable is (in essence) just another name (and type) by which I would like to refer to the value held in this other variable” and allows you to reference the same value as different types without having to laboriously copy that value around from one to the other with all the typecasting that would entail.

      You do of course have to be careful when using it, just as you have to be careful when type-casting any value from one type to another.

    2. Bob Swart’s avatar

      Great examples. I also use the “absolute” keyword from time to time, and your code snippets are great examples. Good post.

    3. Uli Gerhardt’s avatar

      Interesting article. I’m in the fortunate (?) position to work on a program originally written in times when Turbo Pascal didn’t allow to pass records to procedures, so I have some exposure to “absolute” and learnt to appreciate it. One advantage casts have compared with “absolute” is that they offer at least rudimentary checking, e.g. you can’t cast a record to another record typ of different size.

    4. Olaf Monien’s avatar

      Sorry, but my opinion is just “don’t do it”. If you “transport” the examples to the real world, I’m pretty sure that “absolute” will lead to errors, maintenace problems and lots of lost hours (spent on debugging).

      f you are working with hardware and/or drivers, then it may make sense, but in OOP this is just a no-go.

      Sorry for beeing a hard-liner here ;-)

      1. Jolyon Smith’s avatar

        @Olaf: a rather odd observation since the problem is not always one that OO can solve, so insisting on only using “OO” techniques doesn’t really help (e.g. the INTRESOURCE example).

        In some ways absolute is closer to the spirit of OO (modelling a useful software view of the real world) than type casting which is a characteristic of a procedural approach, albeit in the case of class-type casting involves OO, but isn’t really an “OO” approach.

        But thanks for not disappointing. I needed at least one comment to validate my supposition that there would be a dissenting voice. :)

      2. Malcolm Groves’s avatar

        Nice one, Jolyon. I’d almost forgotten about absolute, and could have used it relatively recently if I’d thought of it. Thanks for the reminder.

      3. Cobus Kruger’s avatar

        I also use absolute as a way to do typecasting. Where I am totally horrified to see it, is when it specifies a memory address (absolute $00ef4431, for example). Fortunately, it has been a while since I worked with anyone brave enough to use that :-)

      4. Chris’s avatar

        Joylon – nice examples. Here’s another, on the same lines: certain COM interface definitions (thinking of IDispatch in particular) are, um, not as obvious in their choice of parameter types as they might be, though for backwards compatibility reasons, we’ve been stuck with them since D3. Redeclaring isn’t a viable option (a sceptic should try it out in some ‘real’ code to see what I mean), yet using the absolute directive as you’ve shown makes for an easy (and clean) way to avoid typecasting,. Indeed, it actually adds in type safety that was missing before – e.g., key parameters to IDispatch.Invoke are declared as untyped pointers when they can only ever be pointers to one specific type each.

      5. Ken Knopfli’s avatar

        I still don’t see what we’ve gained.

        This, would be useful,

        component.Text := Uppercase(component.Text)

        but I’m not sure we’ve gained much with the absolute declaration.

        TurboPower Software justified it’s use by saying it’s faster.

        My Borland manual has the following example:

        Str: string[32]:
        StrLen: byte absolute Str;

        …which wasn’t really the way to do it, even back then.

        I’m still curious if there is a more compelling situation where you would want to use it.

      6. Anthony’s avatar

        Those are some very interesting uses of absolute — stuff that had never occurred to me before. I know I’ve got some code for sure that could use it.

      7. Ken Knopfli’s avatar

        …OK, now I get it. In your Schizo types example, a variant type of HiWord and LoWord overlaid on a Pointer type would be a good example of using both absolute and a variant type.

        In fact, I recall having done something similar to this to do endian conversion (way back when).

      8. Xepol’s avatar

        @Ken -> You are just telling the compiler to use the same address on the stack to represent different variable types. Since, in the case of TEdit, TMemo etc, all that exists on the stack is a pointer, you are effectively telling the compiler to use the pointer at that location as if it was a pointer to TEdit, TMemo etc. It’s a type of type cast.

        In the case of
        Str: string[32]:
        StrLen: byte absolute Str;

        that is ABSOLUTELY the way many of us did it in the day. Short strings were limited to 255 characters because the zero’th position of the string was a byte that contained its length. It was quicker to just access the 0th element of the string than call the Length function. IN those days, it mattered, particularilly when you did a LOT of string handling.

        Yes, Delphi 2’s ANsiString caused a degree of migration pain for those of us who did this a lot. It wasn’t the correct way, and we payed for it when things changed. Fortunately, it was impossible to compile the old code in the new string style so you HAD to fix it (which was a relatively painless and predictable fix) before your code could run – made the outcome very predictable.

        Interesting note, this use of the ABSOLUTE keyword is also not the intended use but rather actually allowed you to tell the compiler the absolute location in memory for a variable.

        In the old DOS days, this was very VERY handy – the most common use I saw was to overlay a char array onto the video buffer (text and graphics).

        Since Windows came along, it has been pretty much restricted to the stack thing.

      9. Gerry’s avatar

        I also use absolute for many of these cases, mainly in sort compare functions as mentioned by Xepol, and sometimes with for..in.

        Unfortunately I work with a programmer who uses the for..in method, but calls the pointer p (and sometimes p1, p2 etc) – it’s amazing how hard a bad naming convention can make code to read!

      10. Turbo Pascal Fan’s avatar

        The potential for making complex things simple and clear is much bigger than the potential for making mistakes. It is always the choice of the programmer which technique he will use, so if he is confident that using absolute is the most elegant way to achieve something then this is obviously not the potential for making mistakes.

      11. Ken Knopfli’s avatar

        @Xepol: Thanks!
        “that is ABSOLUTELY the way many of us did it in the day”

        I remember doing it like this:

        var StringLength: byte;
        StringLength := MyString[0];

        I think the compatible type char=byte meant no casting was necessary by the time BP7 arrived. Older Turbo Pascals were much stricter, hence the “absolute” technique, but it was an anachronism in BP7.

      12. Kryvich’s avatar

        I read about the Absolute keyword that it can break an optimization of your code…

        Yes, it is:

        A technique sometimes used to avoid typecasting is to “overlay” a variable with another of a different type by using the absolute keyword. However, this prevents the variable from becoming a “fast” register variable. It is better to type-cast and save the original variable into a new variable.

        http://effovex.com/OptimalCode/general.htm

        1. Jolyon Smith’s avatar

          @Kryvich: An interesting link and a lot of the info there makes sense.

          I’m not so sure about some though – the comments about try/finally adding overhead for example… I thought I’d read somewhere/somewhen else that try/finally or try/except *doesn’t* add any overhead unless an exception is actually thrown.

          It’s also not entirely clear whether the author means that “absolute” affects parameters or local variables – he refers to variables but his example shows a parameter. I don’t think he can mean variables because afaik variables are *always* declared on the stack and if he means parameters then I’d be interested to do some further investigation. Perhaps my understanding of calling conventions is incomplete, but in respect of event handlers in particular I’m curious as to how one handler can be using a register for a parameter that another handler for the same event expects on the stack.

          Generally I’d be careful about taking too much of that article as “gospel” without confirming any relevant observations in your own test harnesses – it was written almost 6 years ago after all.

        2. Kryvich’s avatar

          It’s not about parameters, but it’s about the register optimization inside a code block.

          Some people think it’s a Greatest Delphi article ever written:
          https://forums.codegear.com/thread.jspa?threadID=15794

          So I would trust it.

        3. Franck’s avatar

          Great article. I manipulate a lot of pointer in my application, the keyword “absolute” will simplify a lot of my functions.
          Just a little mistake in the first example of paragraph “To End At The Beginning” :
          1.procedure PrintFormats( aFormatList: TAddressFormatList );
          2.var
          3. addrFormat: TAddressFormat;
          4.begin
          5. for addrFormat in addrFormats do
          6. WriteLn( addrFormat.Description );
          7.end;

          I think line 5 must be replace by “for addrFormat in aFormatList do”

        4. WarrenP’s avatar

          Count me firmly in the “Absolute is worse than Goto” camp. Reasoning:

          A casual reader of the code, even a very experienced delphi language user, might be confused about its behavior.

          It’s an assembly language directive moved into TurboPascal, and it breaks strong typing. As such the argument in its favor is that it is no better, and no worse (in terms of pure type safety) than a typecast, but it is worse, because it does not have locality. An explicit type-cast is EXPLICIT, and readable and has locality of effect. Absolute has no such locality of effect. Reading code without checking for the presence of absolute keywords, could lead to vast misunderstandings while reading the code.

          Just as the WITH statement is adored by some and reviled by others, it creates an effect over an area, that might include side effects that you hadn’t thought of. Another comment above mentions compiler optimizations. I am thinking not only of registers, but of alignment, packing, and other things. If you port code to 64 bit delphi which assumes (via absolute) that sizeof(Integer)=sizeof(pointer) for example, it will not break or complain or even generate a warning. You could fix it by changing such Ints to NativeInt, but my, what a nice source of HeisenBugs.

          Warren

        5. Deltics’s avatar

          @Warren… your reasoning makes no sense.

          1. .. assembly language directive moved into Pascal.

          Any number of syntactic elements can be said to be “assembly language.. moved into..” a higher level language. Presumably you never use “if A = B” since this is just an assembly language operation moved into the higher level language ? Yes ?

          2. .. worse because it does not have locality.

          This is just as much an argument FOR it. If you have to type cast repeatedly throughout a routine then this increases the chances that you will get one of those type casts wrong and certainly makes the code LESS readable, not more. You make the common and unforgivable mistake of using “readable” in your argument incorrectly to mean “repetitious and unrelentingly unambiguous”. If novels were written in the style that your use of the term “readable” is meant here, nobody would read them.

          3. reading code could lead to misunderstandings.

          This is true of any situation where someone doesn’t read ALL of the relevant code, and simply makes assumptions about the code they haven’t read. Reading code that references symbols without knowing what those symbols are is a recipe for misunderstanding, regardless of the use of absolute or not. Assumptions is what leads to misunderstandings, not particular language features.

          4. adored by some, reviled by others.

          Why do you have to make it so black and white? I do not revile “with”. neither do I “adore” it. But I absolutely refuse to accept that there are not times when it can be safely, sensibly and reliably used. Ditto absolute. Ditto just about ANY language feature you care to mention.

          5. porting code to 64-bit with assumptions.

          Sorry, but even your more acceptable explicit pointer/integer casting code would fall foul of that one. Again, the problem is in the ASSUMPTION, leading to the code, not (strictly speaking) the code itself. The code does what you tell it to do. Tell it wrong, and it will be wrong. But whether you “tell it” by using “absolute” or typecasting, the mistake is in the telling.

          And I shall point out here that in that situation, there is a much more highly desirable “locality effect” when using absolute. Once you recognise the error (an Integer variable declared absolute to a pointer variable) you can fix the code in one simply change: simply change the Integer declaration to NativeInt. If the same assumption is built into numerous typecasts scattered through the routine, then you will have to make very sure that each and every single one of them is individually fixed.

          Miss even one and your Heisenbugs will continue.

        6. Deltics’s avatar

          Commenter MaBuse asked me to post his comment here since the original article is now past the window where comments are allowed (an anti-spambot measure):

          MaBuse says:

          I used the absolute keyword in Turbo Pascal under DOS to access hardware (screen, serial port, printer port, …) ;-)

          This is in Windows since Win NT not posible.

          For example:

          var screen: array[1..4000] of byte absolute $B8000:0000
          

          gives me access to screen memory in textmode.

          Alternate you can define:

          var screen: array[1..25,1..80,0..1] of byte absolute $B8000:0000
          screen[row, line,0] := TextAttribute; // color
          screen[row, line,1] := ord(‘X’); // Text Char
          

Comments are now closed.