Thinking In Pointers
September 11, 2013 Ted Holt
Suppose you were to ask me how large an alphanumeric variable should be, and I replied that I didn’t know, nor did I care. Wouldn’t that sound odd? Those of us who have been programming in business languages such as RPG, COBOL, and CL since the French and Indian War always think it’s important to know the size of a variable, otherwise we won’t be able to define it properly in a program. But when you work with pointers, a variable’s defined size doesn’t necessarily matter. Let me show you what I’m talking about.
Consider a command:
CMD PROMPT('Do something') PARM KWD(OBJECT) TYPE(*CHAR) LEN(10) MAX(12) + EXPR(*YES) PROMPT('List of objects')
Here’s the parameter list of the CL command-processing program.
How long must &inList be? At least 122 bytes, in order to allow for a maximum of 12 elements. The first two bytes contain a binary number, the value of which is the number of elements in the list. The remainder of the parameter consists of 10-byte values. Twelve times 10 bytes per element of the list, plus two bytes for the binary prefix, is 122.
pgm parm(&inList) dcl &inList *char 122
The command processor may send 122 bytes to the program, or it may send fewer than 122 bytes. For instance, if the user keys in three object names, the command processor will send 32 bytes to the program, and it is important that the program not access the memory assigned to bytes 33 through 122.
Here is the not-quite-complete command-processing program, written in CL.
pgm parm(&inList) dcl &inList *char 122 dcl &Ndx *int 2 dcl &ListSize *int 2 dcl &Offset *int 2 dcl &CurElement *char 10 chgvar &ListSize %bin(&inList 1 2) chgvar &Offset 3 dofor &Ndx from(1) to(&ListSize) chgvar &CurElement %sst(&inList &Offset 10) /* do something with &CurElement */ chgvar &Offset (&Offset + 10) enddo
Each iteration of the DOFOR loop retrieves the next list value into variable &CurElement. The program does something with &CurElement, but whatever it does is irrelevant to this discussion.
One day someone decides that 12 occurrences are not enough. You double the value of the MAX parameter, to 24.
CMD PROMPT('Test list processing') PARM KWD(OBJECT) TYPE(*CHAR) LEN(10) MAX(24) + EXPR(*YES) PROMPT('List of names')
Now you have to modify the CL program. Let’s see. The &inList variable must be assigned the value of 24 times 10, plus 2, or 242.
Now permit me to encourage you to think about programming in a way you may not be accustomed to. (This may be analogous to asking a native speaker of English to think in Chinese, but bear with me.)
When we process the list, how many bytes of the list do we care about at any one time? The answer is 10. We never care about more than 10 bytes at any one time.
chgvar &CurElement %sst(&inList &Offset 10) /* do something with &CurElement */
So why define a 122-byte variable when 10 bytes will suffice? Look at this version of the same command-processing program.
pgm parm(&inList) dcl &inList *char 1 dcl &pInList *ptr dcl &ListSize *int 2 stg(*based) basptr(&pInList) dcl &pElement *ptr dcl &CurElement *char 10 stg(*based) basptr(&pElement) dcl &Ndx *int 2 chgvar &pInList %addr(&inList) chgvar &pElement &pInList chgvar %offset(&pElement) (%offset(&pElement) + 2) dofor &Ndx from(1) to(&ListSize) /* do something with &CurElement */ chgvar %offset(&pElement) (%offset(&pElement) + 10) enddo
The &inList variable has been defined with a length of 1, which is clearly and incontrovertibly inaccurate. I’ve defined it that way because I don’t care what the length of the complete list is. All I care about is the parameter’s address in memory.
Notice that I’ve defined two pointer variables–&pInList and &pElement. Let’s look at the role of &pInList first.
The first Change Variable (CHGVAR) command sets &pInList to the memory address of parameter &inList. Since variable &ListSize is based on &pInList, &ListSize also points to the area of memory where the list begins. Since the first two bytes of the parameter contain the number of elements in the list, the value of &ListSize, then, is the number of elements in the list.
The values in the list begin in position three of the list, so that’s the reason for the next two CHGVAR commands. Since &CurElement is based on &pElement, the value of &CurElement is the value of the first value in the list when the DOFOR begins. Each iteration of the loop advances the &pElement 10 bytes, which makes &CurElement overlay the next element in the list.
At the risk of belaboring the point, here’s my attempt at an illustration.
Suppose someone runs this command as follows:
DOIT OBJECT(SQUEEZLE PUCKERT SNOOFLE GRIPPICK)
Suppose also that the system loads the list into memory at address 101. Here are the contents of memory.
Variable &pInList is 101. At the beginning of each iteration of the loop, &pElement has the values 103, 113, 123 and 133.
And what change must be made to this program when the MAX value in the command is increased from 12 to 24? Why, none at all! Is that fine, or what?
By the way, if this example looks familiar, you may have seen it in an article I wrote about RPG and basing pointers. See the Related Story below for details.
Pointers are unfamiliar territory to many programmers, but they don’t have to be. Learn to use them. Besides being useful, they’re also fun!