Contributor's Corner

A collection of hopefully helpful information
Functionality Clarity Elegance

Pointers


Pointer Arithmetic

Your system’s memory, presuming it’s typical, is a collection of storage locations which are numbered sequentially from beginning to end so that access is simplified. It amounts to one long contiguous array. (It is typically implemented physically as a 2D array of bits, but that's another subject.) Part of that address space might not be "stuffed" with actual memory, or the locations may, via hardware machinations, be locations in other physical devices. One doesn't want to try to address those parts unknowingly. Many systems will protect them with the appropriate permissions.

On its journey from your fingertips to its final use, the representation of a variable, say iValue, will change. At some points it may be construed as a value relative to some location in your program. It will, sooner or later, become a binary pattern at an absolute position within the system’s memory.

If that final position is in element 4096, it may be found in an infinite number of ways. Locations4095+1, 1+4095 and 96+4000 will all result in the same answer. One's arrays also have multiple locations. One chooses among them by pointing, sometimes with the notation, iArray[n] The value of the effective address of this expression may be had by summing a value represented by the token outside the braces with a value represented by the token inside the braces. One cannot tell whether iArray is the name of an array or the name of a pointer in the context of this particular paragraph. One must look back. Fortunately, compilers keep good notes, because the "value represented by the token..." is obtained in different ways for the two possibilities.

It is considered bad practice to reverse the array notation, as in, 5[iArray], but it is nonetheless valid. There is no dearth of high priests and gurus, each with a sackful of such dictums, chomping at the bit to say you nay. Use your own judgment and try to make it reasonably sensible. The relevant point is that the compiler chooses to use the value in the brackets as a quantity to add as an offset to the value before the brackets. iArray+5 results in the same answer as 5+iArray. This is simple and while it may represent something you haven’t thought about previously, it isn’t a confustoid. The confustoid occurs because of what the compiler chooses to do behind the scenes with the item, iArray. If iArray is the name of an array, the compiler will take the address of its beginning element and add the proper offset to it. If iArray is the name of a pointer, the compiler will take the VALUE from the pointer variable and add the offset. Confustoids incoming!!

Suppose you declare an array and use it,

int myArray [32]; 
myArray [64] = 1257;
It is obvious that the size is 32. It is obvious that the second statement exceeds the bounds, possibly with an extremely detrimental effect. Unless your compiler is better than mine, it will not warn you! If you aren't safe in this exceedingly local contextual scope, why would you expect to be safe off in the hinterlands of a remote function to whom you have passed a reference (pointer) to myArray? Observing the bounds is your responsibility and you shirk it at your peril. The mere notation provides no protection beyond that available with...
int myArray [32]; 
int *pArray = myArray; 
pArray = pArray+64; 
*pArray = 1257;
...which is none.

One thing the compiler will do for you is observe the size of your elements. If your integer is 4 bytes the compiler will see to it that an effective address calculation will move 4 bytes for each increment, whether it's implemented as an explicit addition or by one of the incrementation operators. In order to do this for you, the compiler must know that the reference item, whether direct address or pointer, refers to an integer-sized object. This means that it is your responsibility to declare the arguments to your functions properly if you want the information available inside the function.

There is this about misusing your pointers: you may destroy things without immediate and noticable effect. Quite often the result of misuse becomes apparent as soon as run-time arrives. This is not necessarily the case. Information that one inadvertently destroys may have been used for a task that has been completed. It might not be attempted again, or, if it is, it might regenerate its data. Information that one destroys may not be detrimental to the operation of the program except in rare circumstances of low probability. Mr. Murphy says those circumstances will occur at the most damaging moment.

When a program is being compiled and tested in debug mode, which tends to be the time one is looking hardest for problems, there is a lot of non-critical (to the functioning of the application) information shoe-horned into nearby memory in the form of bookkeeping information. Overwriting this information may never be noticed. If it's noticed, it may be ignored. There are plenty of times when a watched variable may indicate "bad pointer" when things are precisely as they should be.

If your misuse or molestation of a pointer is brought to your attention, and your response is, "It has to be okay procedure, the program works and doesn't crash," then you need an attitude adjustment. A whop upside the head with a 2 x 4 would not be amiss.

 What is not a Pointer?Sneaky Pointers