1. The Visual C Compiler, Version 6

Chapter 10 - Using Pointers

Visual C++ 6: The Complete Reference
Chris H. Pappas and William H. Murray, III
  Copyright 1998 The McGraw-Hill Companies

Pointer Variables
Another (and often more convenient and efficient) way to access a variable is through a second variable that holds the address of the variable you want to access. Chapter 8 introduced the concept of pointer variables, which are covered in more detail in this chapter. For example, suppose you have an int variable called imemorycell_contents and another variable called pimemorycell_address (admittedly verbose, but highly symbolic) that can hold the address of a variable of type int. In C/C++, you have already seen that preceding a variable with the & address operator returns the address of the variable instead of its contents. Therefore, the syntax for assigning the address of a variable to another variable of the type that holds addresses should not surprise you:
pimemorycell_address = &imemorycell_contents;
Figure 10-1: An example pointer variable
A variable that holds an address, such as pimemorycell_address, is called a pointer variable, or simply a pointer. Figure 10-1 illustrates this relationship. The variable imemorycell_contents has been placed in memory at address 7751. After the preceding statement is executed, the address of imemorycell_contents will be assigned to the pointer variable pimemorycell_address. This relationship is expressed in English by saying that pimemorycell_address points to imemorycell_contents. Figure 10-2 illustrates this relationship. The arrow is drawn from the cell that stores the address to the cell whose address is stored.
Figure 10-2: The pointer variable pimememorycell_address pointing to imemorycell_contents
Accessing the contents of the cell whose address is stored in pimemorycell_address is as simple as preceding the pointer variable with an asterisk: *pimemorycell_address. What you have done is dereference the pointer pimemorycell_address. For example, if you execute the following two statements, the value of the cell named imemorycell_contents will be 20 (see Figure 10-3).
Figure 10-3: Using a pointer variable in an assignment statement
pimemorycell_address = &imemorycell_contents;
*pimemorycell_address = 20;
You can think of the * as a directive to follow the arrow (see Figure 10-3) to find the cell referenced. Notice that if pimemorycell_address holds the address of imemorycell_contents, then both of the following statements will have the same effect; that is, both will store the value of 20 in imemorycell_contents:
imemorycell_contents = 20;
*pimemorycell_address = 20;
Declaring Pointers
C/C++, like any other language, requires a definition for each variable. To define a pointer variable pimemorycell_address that can hold the address of an int variable, you write
int *pimemorycell_address;
Actually, there are two separate parts to this declaration. The data type of pimemorycell_address is
int *
and the identifier for the variable is
pimemorycell_address
The asterisk following int means “pointer to.” That is, the following data type is a pointer variable that can hold an address to an int:
int *
This is a very important concept to remember. In C/C++, unlike many other languages, a pointer variable holds the address of a particular data type.
Let’s look at an example:
char *pcaddress;
int
*piaddress;
The data type of pcaddress is distinctly different from the data type of the pointer variable piaddress. Run-time errors and compile-time warnings may occur in a program that defines a pointer to one data type and then uses it to point to some other data type. It would be poor programming practice to define a pointer in one way and then use it in some other way. For example, look at the following code segment:
int *pi;
float real_value = 98.26;
pi = &real_value;
Here pi is defined to be of type int *, meaning it can hold the address of a memory cell of type int. The third statement attempts to assign pi the address, &real_value, of a declared float variable.
Using Pointer Variables
The following code segment exchanges the contents of the variables iresult_a and iresult_b but uses the address and dereferencing operators to do so:
int iresult_a = 15, iresult_b = 37, itemporary;
int
*piresult;

piresult = &iresult_a;
itemporary =
*piresult;
*piresult = iresult_b;
iresult_b = itemporary;
The first line of the program contains standard definitions and initializations. The statement allocates three cells to hold a single integer, gives each cell a name, and initializes two of them (see Figure 10-4). For discussion purposes, assume that the cell named iresult_a is located at address 5328, the cell named iresult_b is located at address 7916, and the cell named itemporary is located at address 2385.
Figure 10-4: Creation and initialization of memory cells
The second statement in the program defines piresult to be a pointer to an int data type. The statement allocates the cell and gives it a name (placed at address 1920). Remember, when the * is combined with the data type (in this case, int), the variable contains the address of a cell of the same data type. Because piresult has not been initialized, it does not point to any particular int variable. If your program were to try to use piresult, the compiler would not give you any warning and would try to use the variable’s garbage contents to point with. The fourth statement assigns piresult the address of iresult_a (see Figure 10-5).
Figure 10-5: Assigning piresult the address of iresult_a
The next statement in the program uses the expression *piresult to access the contents of the cell to which piresult points—iresult_a:
itemporary = *piresult;
Therefore, the integer value 15 is stored in the variable itemporary (see Figure 10-6). If you left off the * in front of piresult, the assignment statement would illegally store the contents of piresult the address 5328—in the cell named itemporary, but itemporary is supposed to hold an integer, not an address. This can be a very annoying bug to locate since many compilers will not issue any warnings/errors. (The Visual C/C++ compiler issues the warning “different levels of indirection.”)
Figure 10-6: Using Piresult to assign itemporary value
To make matters worse, most pointers are near, meaning they occupy 2 bytes, the same data size as a PC-based integer. The fifth statement in the program copies the contents of the variable iresult_b into the cell pointed to by the address stored in piresult (see Figure 10-7):
Figure 10-7: Another assignment using piresult
*piresult = iresult_b;
The last statement in the program simply copies the contents of one integer variable, itemporary, into another integer variable, iresult_a (see Figure 10-8). Make certain you understand the difference between what is being referenced when a pointer variable is preceded (*piresult) and when it is not preceded (piresult) by the dereference operator *. For this example, the first syntax is a pointer to a cell that can contain an integer value. The second syntax references the cell that holds the address to another cell that can hold an integer.
Figure 10-8: Normal integer assignment
The following short program illustrates how to manipulate the addresses in pointer variables. Unlike the previous example, which swapped the program’s data within the variables, this program swaps the addresses to where the data resides:
char cswitch1 = ‘S’, cswitch2 = ‘T’;
char
*pcswitch1, *pcswitch2, *pctemporary;

pcswitch1   = &cswitch1;
pcswitch2   = &cswitch2;
pctemporary = pcswitch1;
pcswitch1   = pcswitch2;
pcswitch2   = pctemporary;
printf( “%c%c”,
*pcswitch1, *pcswitch2);
Figure 10-9: Starting relationship of program variables
Figure 10-9 shows the cell configuration and values after the execution of the first four statements of the program. When the fifth statement is executed, the contents of pcswitch1 are copied into pctemporary so that both pcswitch1 and pctemporary point to cswitch1 (see Figure 10-10).
Figure 10-10: pctemporary is assigned the address of cswitch1
Executing the following statement copies the contents of pcswitch2 into pcswitch1 so that both pointers point to cswitch2 (see Figure 10-11):
Figure 10-11: Assigning pcswitch1 the address in pcswitch2
pcswitch1 = pcswitch2;
Notice that if the code had not preserved the address to cswitch1 in a temporary location, pctemporary, there would be no pointer access to cswitch1. The next-to-last statement copies the address stored in pctemporary into pcswitch2 (see Figure 10-12). When the printf( ) statement is executed, since the value of *pcswitch1 is “T” and the value of *pcswitch2 is “S,” you will see
TS
Figure 10-12: pcswitch2 is assigned the address in pctemporary
Notice how the actual values stored in the variables cswitch1 and cswitch2 haven’t changed from their original initializations. However, since you have swapped the contents of their respective pointers, *pcswitch1 and *pcswitch2, it appears that their order has been reversed. This is an important concept to grasp. Depending on the size of a data object, moving a pointer to the object can be much more efficient than copying the entire contents of the object.
Initializing Pointers
Pointer variables can be initialized in their definitions, just like many other variables in C/C++. For example, the following two statements allocate storage for the two cells iresult and piresult:
int iresult;
int *piresult = &iresult;
The variable iresult is an ordinary integer variable, and piresult is a pointer to an integer. Additionally, the code initializes the pointer variable piresult to the address of iresult. Be careful: the syntax is somewhat misleading; you are not initializing *PIRESULT (WHICH WOULD HAVE TO BE AN INTEGER VALUE) BUT PIRESULT (WHICH MUST BE AN ADDRESS TO AN INTEGER). THE SECOND STATEMENT IN THE PRECEDING LISTING CAN BE TRANSLATED INTO THE FOLLOWING TWO EQUIVALENT STATEMENTS:
int *piresult;
piresult = &iresult;
The following code segment shows how to declare a string pointer and then initialize it:
/*
*   psz.c
*   A C program that initializes a string pointer and
*   then prints the palindrome backwards then forwards
*   Copyright (c) Chris H. Pappas and William H. Murray, 1998
*/

#include <stdio.h>
#include <string.h>

void main( )
{
 char
*pszpalindrome="MADAM I’M ADAM";
 int i;

 for (i=strlen(pszpalindrome)-1; i >= 0; i—)
   printf(“%c”,pszpalindrome[i]);
   printf(“%s”,pszpalindrome);
}
Technically, the C/C++ compiler stores the address of the first character of the string “MADAM I’M ADAM” in the variable pszpalindrome. While the program is running, it can use pszpalindrome like any other string. This is because all C/C++ compilers create a string table, which is used internally by the compiler to store the string constants a program is using.
The strlen( ) function prototyped in STRING.H calculates the length of a string. The function expects a pointer to a null-terminated string and counts all of the characters up to, but not including, the null character itself. The index variable i is initialized to one less than the value returned by strlen( ) since the for loop treats the string psz like an array of characters. The palindrome has 14 letters. If psz is treated as an array of characters, each element is indexed from 0 to 13. This example program highlights the somewhat confusing relationship between pointers to character strings and arrays of characters. However, if you remember that an array’s name is actually the address of the first element, you should understand why the compiler issues no complaints.
What Not to Do with the Address Operator
You cannot use the address operator on every C/C++ expression. The following examples demonstrate those situations where the & address operator cannot be applied:
/*
  not with CONSTANTS
*/

pivariable = &48;


/
*
  not with expressions involving operators such as + and /
  given the definition int iresult = 5;
*/

pivariable = &(iresult + 15);


/
*
  not preceding register variables
  given the definition register register1;      
*/

pivariable = &register1;
The first statement tries to illegally obtain the address of a hardwired constant value. Since the 48 has no memory cell associated with it, the statement is meaningless.
The second assignment statement attempts to return the address of the expression iresult + 15. Since the expression itself is actually a stack manipulation process, there is no address associated with the expression.
Normally, the last example honors the programmer’s request to define register1 as a register rather than as a storage cell in internal memory. Therefore, no memory cell address could be returned and stored. Microsoft Visual C/C++ gives the variable memory, not register storage.
Pointers to Arrays
As mentioned, pointers and arrays are closely related topics. Recall from Chapter 9 that an array’s name is a constant whose value represents the address of the array’s first element. For this reason, the value of an array’s name cannot be changed by an assignment statement or by any other statement. Given the following data declarations, the array’s name, ftemperatures, is a constant whose value is the address of the first element of the array of 20 floats:
#define IMAXREADINGS 20

float ftemperatures[IMAXREADINGS];
float
*pftemp;
The following statement assigns the address of the first element of the array to the pointer variable pftemp:
pftemp = ftemperatures;
An equivalent statement looks like this:
pftemp = &ftemperatures[0];
However, if pftemp holds the address of a float, the following statements are illegal:
ftemperatures = pftemp;
&ftemperatures[0] = pftemp;
These statements attempt to assign a value to the constant ftemperatures or its equivalent &ftemperatures[0], which makes about as much sense as
10 = pftemp;
Pointers to Pointers
In C/C++, it is possible to define pointer variables that point to other pointer variables, which in turn point to the data, such as an integer. Figure 10-13 illustrates this relationship; ppi is a pointer variable that points to another pointer variable whose contents can be used to point to 10.
Figure 10-13: A pointer to a pointer that points to an integer
You may be wondering why this is necessary. The arrival of Windows and the Windows NT programming environment signals the development of multitasking operating environments designed to maximize the use of memory. To minimize the use of memory, the operating system has to be able to move objects in memory. If your program points directly to the physical memory cell where the object is stored and the operating system moves it, disaster will strike. Instead of pointing directly to a data object, your application points to a memory cell address that will not change while your program is running (for example, let’s call this a virtual_address), and the virtual_address memory cell holds the current_physical_address of the data object. Now, whenever the operating environment wants to move the data object, all the operating system has to do is update the current_physical_address pointed to by the virtual_address. As far as your application is concerned, it still uses the unchanged address of the virtual_address to point to the updated address of the current_physical_address.
To define a pointer to a pointer in C/C++, you simply increase the number of asterisks preceding the identifier:
int **ppi;
In this example, the variable ppi is defined to be a pointer to a pointer that points to an int data type. ppi‘s data type is
int **
Each asterisk is read “pointer to.” The number of pointers that must be followed to access the data item or, equivalently, the number of asterisks that must be attached to the variable to reference the value to which it points, is called the “level of indirection” of the pointer variable. A pointer’s level of indirection determines how much dereferencing must be done to access the data type given in the definition. Figure 10-14 illustrates several variables with different levels of indirection.
Figure 10-14: Using different levels of indirection
The first four lines of code in Figure 10-14 define four variables: the integer variable ivalue, the pi pointer variable that points to an integer (one level of indirection), the ppi variable that points to a pointer that points to an integer (two levels of indirection), and pppi, illustrating that this process can be extended beyond two levels of indirection. The fifth line of code is
pi = &ivalue;
This is an assignment statement that uses the address operator. The expression assigns the address of &ivalue to pi. Therefore, pi‘s contents contain 1111. Notice that there is only one arrow from pi to ivalue. This indicates that ivalue, or 10, can be accessed by dereferencing pi just once. The next statement, along with its accompanying picture, illustrates double indirection:
ppi = &pi;
Because ppi‘s data type is int **, to access an integer you need to dereference the variable twice. After the preceding assignment statement, ppi holds the address (not the contents) of pi, so ppi points to pi, which in turn points to ivalue. Notice that you must follow two arrows to get from ppi to ivalue.
The last statement demonstrates three levels of indirection:
pppi = &ppi;
It also assigns the address (not the contents) of ppi to pppi. Notice that the accompanying illustration shows that three arrows are now necessary to reference ivalue.
To review, pppi is assigned the address of a pointer variable that indirectly points to an integer, as in the preceding statement. However, ***pppi (the cell pointed to) can only be assigned an integer value, not an address, since ***pppi is an integer:
***pppi = 10;
C/C++ allows pointers to be initialized like any other variable. For example, pppi could have been defined and initialized using the following single statement:
int ***pppi = &ppi;
Pointers to Strings
A string constant such as “File not ready” is actually stored as an array of characters with a null terminator added as the last character (see Figure 10-15). Because a char pointer can hold the address of a character, it is possible to define and initialize it. For example:
Figure 10-15: Null-terminated string in memory
char *psz = “File not ready”;
This statement defines the char pointer psz and initializes it to the address of the first character in the string (see Figure 10-16). Additionally, the storage is allocated for the string itself. The same statement could have been written as follows:
Figure 10-16: Initializing a string pointer
char *psz;
psz = “File not ready”;
Again, care must be taken to realize that psz was assigned the address, not *psz, which points to the “F.” The second example given helps to clarify this by using two separate statements to define and initialize the pointer variable.
The following example highlights a common misconception when dealing with pointers to strings and pointers to arrays of characters:
char *psz = “File not ready”;
char pszarray[] = “Drive not ready”;
The main difference between these two statements is that the value of psz can be changed (since it is a pointer variable), but the value of pszarray cannot be changed (since it is a pointer constant). Along the same line of thinking, the following assignment statement is illegal:
/* NOT LEGAL */
char pszarray[16];
pszarray = “Drive not ready”;
While the syntax looks similar to the correct code in the preceding example, the assignment statement attempts to copy the address of the first cell of the storage for the string “Drive not ready” into pszarray. Because pszarray is a pointer constant, not a pointer variable, an error results.
The following input statement is incorrect because the pointer psz has not been initialized:
/* NOT LEGAL */
char
*psz;
cin >> psz;
Correcting the problem is as simple as reserving storage for and initializing the pointer variable psz:
char sztring[10];
char
*psz = sztring;
cin.get(psz,10);
Since the value of sztring is the address of the first cell of the array, the second statement in the code not only allocates storage for the pointer variable, but it also initializes it to the address of the first cell of the array sztring. At this point, the cin.get( ) statement is satisfied since it is passed the valid address of the character array storage.
Pointer Arithmetic
If you are familiar with assembly language programming, then you are already comfortable with using actual physical addresses to reference information stored in tables. For those of you who are only used to using subscript indexing into arrays, believe it or not, you have been effectively using the same assembly language equivalent. The only difference is that in the latter case you were allowing the compiler to manipulate the addresses for you.
Remember that one of C/C++’s strengths is their closeness to the hardware. In C/C++, you can actually manipulate pointer variables. Many of the example programs seen so far have demonstrated how one pointer variable’s address, or address contents, can be assigned to another pointer variable of the same data type. C/C++ allows you to perform only two arithmetic operations on a pointer address—namely, addition and subtraction. Let’s look at two different pointer variable types and perform some simple pointer arithmetic:
//
//  ptarth.cpp
//  A C++ program demonstrating pointer arithmetic
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>

void main( )
{
 int
*pi;
 float
*pf;

 int an_integer;
 float a_real;

 pi = &an_integer;
 pf = &a_real;

 pi++;
 pf++;

}
Let’s also assume that an integer is 2 bytes and a float is 4 bytes (for 16-bit C/C++ environments). Also, an_integer is stored at memory cell address 2000, and a_real is stored at memory cell address 4000. When the last two lines of the program are executed, pi will contain the address 2002 and pf will contain the address 4004. But wait a minute—didn’t you think that the increment operator ++ incremented by 1? This is true for character variables, but not always for pointer variables.
In Chapter 6, you were introduced to the concept of operator overloading. Increment (++) and decrement (- -) are examples of this C/C++ construct. For the immediate example, since pi was defined to point to integers (which, for the system in this example, are 2 bytes), when the increment operation is invoked, it checks the variable’s type and then chooses an appropriate increment value. For integers, this value is 2; for floats, the value is 4 (on the example system). This same principle holds true for whatever data type the pointer is pointing to. Should the pointer variable point to a structure of 20 bytes, the increment or decrement operator would add or subtract 20 from the current pointer’s address.
You can also modify a pointer’s address by using integer addition and subtraction, not just the ++ and - - operators. For example, moving four float values over from the one currently pointed to can be accomplished with the following statement:
pf = pf + 4;
Look at the following program carefully and see if you can predict the results. Does the program move the float pointer pf one number over?
//
//  sizept.cpp
//  A C++ program using sizeof and pointer arithmetic
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
#include <stddef.h>

void main( )
{
 float fvalues[] = {15.38,12.34,91.88,11.11,22.22};
 float
*pf;
 size_t fwidth;

 pf = &fvalues[0];

 fwidth = sizeof(float);

 pf = pf + fwidth;

}
Try using the integrated debugger to single-step through the program. Use the Variables window to keep an eye on the variables pf and fwidth.
Assume that the debugger has assigned pf the address of fvalues and that pf contains an FFCA. The variable fwidth is assigned the sizeof(float) that returns a 4. When you executed the final statement in the program, what happened? The variable pf changed to FFDA, not FFDE. Why? You forgot that pointer arithmetic takes into consideration the size of the object pointed to (4 x (4-byte floats) = 16). The program actually moves the pf pointer over four float values to 22.22.
Actually, you were intentionally misled by the naming of the variable fwidth. To make logical sense, the program should have been written as
//
//  ptsize.cpp
//  The same C++ program using meaningful variable names
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>

void main( )
{
 float fvalues[] = {15.38,12.34,91.88,11.11,22.22};
 float
*pf;
 int inumber_of_elements_to_skip;

 pf = fvalues;

 inumber_of_elements_to_skip = 1;

 pf = pf + inumber_of_elements_to_skip;

}
Pointer Arithmetic and Arrays
The following two programs index into a ten-character array. Both programs read in ten characters and then print out the same ten characters in reverse order. The first program uses the more conventional high-level-language approach of indexing with subscripts. The second program is identical except that the array elements are referenced by address, using pointer arithmetic. Here is the first program:
/*
*   arysub.c
*   A C program using normal array subscripting
*   Copyright (c) Chris H. Pappas and William H. Murray, 1998
*/

#include <stdio.h>

#define ISIZE 10

void main( )
{
 char string10[ISIZE];
 int i;

 for(i = 0; i < ISIZE; i++)
   string10[i]=getchar( );

 for(i = ISIZE-1; i >= 0; i—)
   putchar(string10[i]);
}
Here is the second example:
/*
*   aryptr.c
*   A C program using pointer arithmetic to access elements
*   Copyright (c) Chris H. Pappas and William H. Murray, 1998
*/

#include <stdio.h>

#define ISIZE 10

void main( )
{
 char string10[ISIZE];
 char
*pc;
 int icount;

 pc=string10;

 for(icount = 0; icount < ISIZE; icount++) {
   
*pc=getchar( );
   pc++;
 }

 pc=string10 + (ISIZE - 1);

 for(icount = 0; icount < ISIZE; icount++) {
   putchar(
*pc);
   pc—;
 }
}
Since the first example is straightforward, the discussion will center on the second program, which uses pointer arithmetic. pc has been defined to be of type char *, which means it is a pointer to a character. Because each cell in the array string10 holds a character, pc is suitable for pointing to each. The following statement stores the address of the first cell of string10 in the variable pc:
pc=string10;
The for loop reads ISIZE characters and stores them in the array string10. The following statement uses the dereference operator * to ensure that the target—the left-hand side of this assignment (another example of an lvalue)—will be the cell to which pc points, not pc (which itself contains just an address).
*pc=getchar( );
The idea is to store a character in each cell of string10, not to store it in pc.
To start printing the array backward, the program first initializes the pc to the last element in the array:
pc=string10 + (ISIZE - 1);
By adding 9 (ISIZE - 1) to the initial address of string10, pc points to the tenth element. Remember, these are offsets. The first element in the array is at offset zero. Within the for loop, pc is decremented to move backward through the array elements. Make certain you use the integrated debugger to trace through this example if you are unsure of how pc is modified.
Problems with the Operators ++ and - -
Just as a reminder, the following two statements do not perform the same cell reference:
*pc++=getchar( );
*++pc=getchar( );
The first statement assigns the character returned by getchar( ) to the current cell pointed to by pc and then increments pc. The second statement increments the address in pc first and then assigns the character returned by the function to the cell pointed to by the updated address. Later in this chapter you will use these two different types of pointer assignments to reference the elements of argv.
Using const with Pointers
Just when you think you are getting the hang of it, C/C++ throws you a subtle potential curveball. Look at the following two pointer variable declarations and see if you can detect the subtle differences:
const MYTYPE *pmytype_1;
MYTYPE * const pmytype_2 = &mytype;
The first pointer declaration defines pmytype_1 as a pointer variable that may be assigned any address to a memory location of type MYTYPE. The second declaration defines pmytype_2, as a pointer constant to mytype. Was that enough of a hint?
OK, let’s try that one more time. The identifier pmytype_1 is a pointer variable. Variables can be assigned any value appropriate to their defined type—in this case, pmytype_1 can be assigned any address to a previously defined location of type MYTYPE. Well then, you might ask, what does the const keyword do in the declaration? What that const tells the compiler is this: While pmytype_1 may be assigned any address to a memory cell of type MYTYPE, when you use pmytype_1 to actually point to a memory location, what you point to cannot be changed. The following sample statements highlight these subtleties:
pmytype_1 = &mytype1; // legal
pmytype_1 = &mytype2; // legal
*pmytype_1 = (MYTYPE)some_legal_value; // illegal attempting to
              change contents
Now, compare pmytype_1 with pmytype_2, which is declared as a pointer constant. In other words, pmytype_2 can hold the address to a memory location of type MYTYPE. However, it is a locked address. Therefore, pmytype_2 must be initialized to hold a valid address when the pointer constant is declared ( = &mytype;). On the other hand, the contents of the memory location pointed to by pmytype_2 are not locked. Look at the following statements, which highlight these subtleties:
pmytype_2 = &mytype_n; // illegal, attempting to change locked
            pointer address
*pmytype_2 = (MYTYPE) some_legal_value_1; // legal to change memory
              contents
*pmytype_2 = (MYTYPE) some_legal_value_n; // legal to change memory
              contents
Now, of the two uses for the const keyword with pointer declarations, which do you think is closest to an array declaration? Answer: the second use of const, as in pmytype_2‘s declaration. Remember, the name of an array is a locked address to the array’s first element:
int iarray[ SIZE ];
For this reason, the compiler views the identifier iarray as if you had actually declared it as
int * const iarray = &array[0];
Comparing Pointers
You have already seen examples demonstrating the effect of incrementing and decrementing pointers using the ++ and - - operators and the effect of adding an integer to a pointer. There are other operations that may be performed on pointers. These include
  Subtracting an integer from a pointer
  Subtracting two pointers (usually pointing to the same object)
  Comparing pointers using a relational operator such as <=, =, or >=
Since (pointer – integer) subtraction is so similar to (pointer + integer) addition (these have already been discussed by example), it should be no surprise that the resultant pointer value points to a storage location for integer elements before the original pointer.
Subtracting two pointers yields a constant value that is the number of array elements between the two pointers. This assumes that both pointers are of the same type and initially point into the same array. Subtracting pointers that are not of the same type or that initially point to different arrays will yield unpredictable results.
  Note No matter which pointer arithmetic operation you choose, there is no check to see if the pointer value calculated is outside the defined boundaries of the array.
Pointers of like type (that is, pointers that reference the same kind of data, like int and float) can also be compared to each other. The resulting TRUE (!0) or FALSE (0) can either be tested or assigned to an integer, just like the result of any logical expression. Comparing two pointers tests whether they are equal, not equal, greater than, or less than each other. One pointer is less than another pointer if the first pointer refers to an array element with a lower number subscript. (Remember that pointers and subscripts are virtually identical.) This operation also assumes that the pointers reference the same array.
Finally, pointers can be compared to zero, the null value. In this case, only the test for equal or not equal is valid since testing for negative pointers makes no sense. The null value in a pointer means that the pointer has no value, or does not point to anything. Null, or zero, is the only numeric value that can be directly assigned into a pointer without a type cast.
It should be noted that pointer conversions are performed on pointer operands. This means that any pointer may be compared to a constant expression evaluating to zero and any pointer may be compared to a pointer of type void *. (In this last case, the pointer is first converted to void *.)
Pointer Portability
The examples in this section have represented addresses as integers. This may suggest to you that a C/C++ pointer is of type int. It is not. A pointer holds the address of a particular type of variable, but a pointer itself is not one of the primitive data types int, float, and the like. A particular C/C++ system may allow a pointer to be copied into an int variable and an int variable to be copied into a pointer; however, C/C++ does not guarantee that pointers can be stored in int variables. To guarantee code portability, the practice should be avoided.
Also, not all arithmetic operations on pointers are allowed. For example, it is illegal to add two pointers, to multiply two pointers, or to divide one pointer by another.
Using sizeof with Pointers Under 16-bit DOS Environments
  Note The following section describes old, outdated keywords. These pointer size modifiers are no longer needed under the newer 32-bit C/C++ compilers and operating systems. Under a 32-bit operating system and C/C++ compiler, all addresses are a full 32 bits (equivalent to the _ _far and _ _huge modifiers described below). However, as many readers already know, you often encounter code, for purposes of reference or modification, that is written in historic C/C++, ANSI C/C++, and so forth. For this reason, the discussion of these old-style keywords is presented here.
The actual size of a pointer variable depends on one of two things: the size of the memory model you have chosen for the application or the use of the nonportable, implementation-specific _ _near, _ _far, and _ _huge keywords.
The 80486 to 8088 microprocessors use a segmented addressing scheme that breaks an address into two pieces: a segment and an offset. Many local post offices have several walls of post office boxes, with each box having its own unique number. Segment:offset addressing is similar to this design. To get to your post office box, you first need to know which bank of boxes, or wall, yours is on (the segment), and then the actual box number (the offset).
When you know that all of your application’s code and data will fit within a single 64K of memory, you choose the small memory model. Applying this to the post office box metaphor, this means that all of your code and data will be in the same location, or wall (segment), with the application’s code and data having a unique box number (offset) on the wall.
For those applications where this compactness is not feasible, possibly because of the size and the amount of data that must be stored and referenced, you would choose a large memory model. Using the analogy, this could mean that all of your application’s code would be located on one wall, while all the data would be on a completely separate wall.
When an application shares the same memory segment for code and data, calculating an object’s memory location merely involves finding out the object’s offset within the segment. This is a very simple calculation.
When an application has separate segments for code and data, calculating an object’s location is a bit more complicated. First, the code or data’s segment must be calculated, and then its offset within the respective segment. Naturally, this requires more processor time.
C++ also allows you to override the default pointer size for a specific variable by using the keywords _ _near, _ _far, and _ _huge. Note, however, that by including these in your application, you make your code less portable since the keywords produce different results on different compilers. The _ _near keyword forces an offset-only pointer when the pointers would normally default to segment:offset. The _ _far keyword forces a segment:offset pointer when the pointers would normally default to offset-only. The _ _huge keyword also forces a segment:offset pointer that has been normalized. The _ _near keyword is generally used to increase execution speed, while the _ _far keyword forces a pointer to do the right thing regardless of the memory model chosen.
For many applications, you can simply ignore this problem and allow the compiler to choose a default memory model. But eventually you will run into problems with this approach—for example, when you try to address an absolute location (some piece of hardware, perhaps, or a special area in memory) outside your program’s segment area.
On the other hand, you may be wondering why you can’t just use the largest memory model available for your application. You can, but you pay a price in efficiency. If all of your data is in one segment, the pointer is the size of the offset. However, if your data and code range all over memory, your pointer is the size of the segment and the offset, and both must be calculated every time you change the pointer. The following program uses the function sizeof( ) to print out the smallest pointer size and largest pointer size available.
This C++ program prints the default pointer sizes, their _ _far sizes, and their _ _near sizes. The program also uses the stringize preprocessor directive (#) with the A_POINTER argument, so the name as well as the size of the pointer will be printed.
//
//  strize.cpp
//  A C++ program illustrating the sizeof(pointers) and
//  program is only valid under 16-bit C/C++ environments.
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <stdio.h>

#define PRINT_SIZEOF(A_POINTER) \
 printf(“sizeof\t(”#A_POINTER")\t= %d\n", \
 sizeof(A_POINTER))

void main( )
{
 char
*reg_pc;
 long double
*reg_pldbl;
 char _ _far
*far_pc;
 long double _ _far
*far_pldbl;
 char _ _near
*near_pc;
 long double _ _near
*near_pldbl;

 PRINT_SIZEOF(reg_pc);
 PRINT_SIZEOF(reg_pldbl);
 PRINT_SIZEOF(far_pc);
 PRINT_SIZEOF(far_pldbl);
 PRINT_SIZEOF(near_pc);
 PRINT_SIZEOF(near_pldbl);
}
The output from the program looks like this:
sizeof   (reg_pc)      = 2
sizeof   (reg_pldbl)   = 2
sizeof   (far_pc)      = 4
sizeof   (far_pldbl)   = 4
sizeof   (near_pc)     = 2
sizeof   (near_pldbl)  = 2

Books24x7.com, Inc 2000 –  


Visual C++ 6(c) The Complete Reference
Visual Studio 6: The Complete Reference
ISBN: B00007FYGA
EAN: N/A
Year: 1998
Pages: 207

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net