Chapter 18

Taking a Second Look at C++ Pointers

In This Chapter

arrow Defining operations on a pointer

arrow Comparing pointer addition with indexing an array

arrow Extending arithmetic to different types of pointers

arrow Sorting out constant pointers from pointers to constants

arrow Reading the arguments to a program

Chapter 17 introduces the concept of a pointer variable as a variable designed to contain the address of another variable, and even suggests a couple of uses for pointer variables. However, you’ve only begun to see the myriad ways that pointer variables can be used to do some pretty cool stuff — and really confuse you at times as well.

This chapter examines carefully the relationship between pointers and arrays, a topic that I brush over in the previous chapter.

Pointers and Arrays

Some of the same operators applicable to integers are applicable to pointer types. This section examines the implications of this to pointers and to the array types studied so far.

Operations on pointers

Table 18-1 lists the fundamental operations that are defined on pointers.

Table 18-1 The Operations Defined on Pointer Type Variables

Operation

Result

Meaning

pointer + offset pointer - offset

pointer

Calculate the address of the object offset entries from the pointer

pointer++ ++pointer pointer-- --pointer

pointer

Move the pointer over one entry

pointer2 - pointer1

offset

Calculate the number of entries between pointer2 and pointer1

Although not listed in Table 18-1, operations that are related to addition and subtraction, such as pointer += offset, are also defined.

The simple memory model used to explain pointers in Chapter 17 will work here to explain how these operations work. Consider an array of 32 one-byte characters called cArray. If the first byte of this array is stored at address 0x1000, then the last location will be at 0x101F. While cArray[0] will be at 0x1000, cArray[1] will be at 0x1001, cArray[2] at 0x1002, and so forth.

Now assume a pointer pArray is located at location 0x1100. After executing the expression

  pArray = &cArray[0];

the pointer pArray will contain the value 0x1000 (see Figure 18-1). By the way, you read this as “pArray gets the address of cArray sub 0.”

9781118823873-fg1801.tif

Figure 18-1: After the assignment pArray = &cArray[0] the pointer pArray points to the beginning of the array cArray.

Adding a value n to pArray generates the address of cArray[n]. For example, consider the case where n equals 2. In that case, pArray + 2 generates the address 0x1002, which is the address of cArray[2]. This correspondence is demonstrated in Table 18-2. Figure 18-2 shows this graphically.

Table 18-2 The Correspondence between Pointer Offsets and Array Elements

Offset

Result

Corresponds to...

+ 0

0x1000

cArray[0]

+ 1

0x1001

cArray[1]

+ 2

0x1002

cArray[2]

...

...

...

+ n

0x1000 + n

cArray[n]

9781118823873-fg1802.tif

Figure 18-2: If pArray points to the beginning of cArray, then pArray + 2 points to cArray[2].

Pointer addition versus indexing into an array

I claim that

  pArray = &cArray[0];
*(pArray + 2) = 'c';

is the same as

  cArray[2] = 'c';

Before you can respond to this claim, I need to explain how to read the first code snippet. Take it one step at a time. You already know to read the first expression: pArray = &cArray[0] means “pArray gets the address of cArray sub 0.”

To interpret the second expression, remember that pArray + 2 generates the value 0x1002, and it is of type char*. *(pArray + 2) on the left-hand side of an assignment operator says, “store a ’c’ in the char pointed at by pArray + 2.” This is demonstrated graphically in Figure 18-3.

9781118823873-fg1803.tif

Figure 18-3: The expression *(pArray + 2) = 'c' stores a 'c' in cArray[2].

remember.eps The parentheses around *(pArray + 2) are necessary because unary * has higher precedence than addition. The expression *pArray + 2 retrieves the character pointed at by pArray and adds 2 to it. Adding the parentheses forces the addition to occur first and the unary operator to be applied to the result.

In fact (here comes the kicker), the correspondence between the two forms of expression is so strong that C++ considers cArray[n] nothing more than shorthand for *(pArray + n) where pArray points to the first element in cArray:

  cArray[n] is interpreted as *(&cArray[0] + n)

To complete this association, C++ takes another shortcut by making the second, following interpretation:

  cArray is interpreted as &cArray[0]

That is, an array name when it appears without a subscript is interpreted as the address of the first element of the array; thus the following:

  cArray[n] is interpreted as *(cArray + n)

In fact, the C++ compiler considers the expression on the left nothing more than some human shorthand for the expression on the right.

So, if I can treat the name of an array as though it were a pointer (which it is, by the way), can I use the index operator on pointer variables? Absolutely. Thus the following is perfectly legal:

  char cArray[256];
char* pArray = cArray;
pArray[2] = 'c';

That is how I can write expressions like the following (as in Chapter 17):

  int nTargetSize = strlen(szSrc1) + strlen(szSrc2) + 1;
char* pszTarget = new char[nTargetSize];

// first copy the first string into the target
int nT;
for(nT = 0; szSrc1[nT] != ''; nT++)
{
    pszTarget[nT] = szSrc1[nT];
}

The variable pszTarget is declared as char* (read “pointer to a char”) because that’s what new char[nTargetSize] returns. The subsequent for loop assigns values to elements in this array using the expression pszTarget[nT], which is the same as accessing char elements pointed at by pszTarget + nT.

remember.eps By the way, the psz prefix is the naming convention for “pointer to an ASCIIZ string.” An ASCIIZ string is a character array that ends with a terminating null character.

Using the pointer increment operator

The following is what you might call the pointer arithmetic version of the concatenateString() function from the ConcatenateHeap program from Chapter 17. This version is part of the program ConcatenatePtr in the online material.

technicalstuff.eps In fact, you deal with pointer arithmetic in Chapter 17 as well but that pointer arithmetic is written using array indexing.

C++ programmers love their pointers. The following explicit pointer version of concatenateString() is much more common than the array index version in Chapter 17:

  // concatenateString - concatenate two strings together
//                     into an array allocated off of the
//                     heap
char* concatenateString(const char* pszSrc1,
                        const char* pszSrc2)
{
    // allocate an array of sufficient length
    int nTargetSize = strlen(pszSrc1)+strlen(pszSrc2)+1;
    char* pszTarget = new char[nTargetSize];

    // first copy the first string into the target
    char* pszT = pszTarget;
    for(; *pszSrc1 != ''; pszT++, pszSrc1++)
    {
        *pszT = *pszSrc1;
    }

    // now copy the contents of the second string onto
    // the end of the first
    for(; *pszSrc2 != ''; pszT++, pszSrc2++)
    {
        *pszT = *pszSrc2;
    }

    // add the terminator to szTarget
    *pszT = '';

    // return the unmodified address of the array
    // to the caller
    return pszTarget;
}

This version of concatenateString() starts out exactly like the earlier ConcatenateHeap version from Chapter 17. The difference between this version and its predecessor lies in the two for loops. The version in Chapter 17 leaves the pointer to the target array, pszTarget, unchanged — and increments an index into that array.

The version that appears here skips the intermediate step of incrementing an index and simply increments the pointer itself. First, it checks to make sure that pszSrc1 doesn’t already point to the null character that indicates the end of the source character string. If not, the assignment within the for loop

  *pszT = *pszSrc1;

says to retrieve the character pointed at by pszSrc1 and store it in the location to which pszT points. This is demonstrated graphically in Figure 18-4.

9781118823873-fg1804.tif

Figure 18-4: The expression *pszT = *pszSrc1 copies the character pointed at by pszSrc1 to the location pointed at by pszT.

The increment clause of the for loop

  pszT++, pszSrc1++

increments both the source pointer, pszSrc1, and the target pointer, pszT, to the next character in the source and destination arrays. This is demonstrated by Figure 18-5.

9781118823873-fg1805.tif

Figure 18-5: The increment clause of the for loop increments both source and destination pointers to the next location in the array.

The remainder of the program is identical to its Chapter 17 predecessor, and the results from executing the program are identical as well:

  This program accepts two strings
from the keyboard and outputs them
concatenated together.

Enter first string: this is a string
Enter the second string: SO IS THIS
Concatentate first string onto the second
Result: <this is a stringSO IS THIS>
Press Enter to continue …

Why bother with array pointers?

The sometimes-cryptic nature of using pointers to manipulate character strings might lead the reader to wonder, “Why do it that way?” That is, what advantage does the char* pointer version of concatenateString() have over the easier-to-read index version?

Note: “Easier-to-read” is a matter of taste. To a seasoned C++ programmer, the pointer version is just as easy to fathom as the index version.

The answer is partially historic and partially a matter of human nature. As complicated as it might appear to the human reader, a statement such as *pszT = *pszSrc1 can be converted into an amazingly small number of machine instructions. Older computer processors weren’t very fast by today’s standards. When C, the progenitor of C++, was introduced to the world some 45 years ago, saving time by saving a few computer instructions was a big deal. Pointer arithmetic gave C a big advantage over other languages of the day, notably Fortran, which did not offer pointer arithmetic. This single feature, more than any other, advanced C (and later C++) over its competitors.

In addition, programmers like to generate clever program statements to combat the boredom of what can be a repetitive job. Once C++ programmers learn how to write compact and cryptic-but-efficient statements, there’s no getting them back to scanning arrays with indices.

warning.eps Don’t fall into the trap of cramming as much as you can into a single C++ statement, thinking that a few C++ source statements will generate fewer machine instructions that will, therefore, execute faster. In the old days, when compilers were simpler, that may have worked, but today there’s no obvious relationship between the number of C++ instructions and the number of machine instructions generated. For example, the expression

  *pszT++ = '';

does not necessarily generate machine instructions that are any different from those that come from the following expression (which is both easier to read and easier to debug):

  *pszT = '';
pszT++;

Today’s optimizing compilers generate minimal amounts of code.

Operations on Different Pointer Types

It’s not too hard to convince yourself that pszTarget + n points to pszTarget[n] when each element in the array is 1 byte in length as is the case for char strings. After all, if cArray is located at 0x1000, then cArray[5] must be at 0x1005.

It is not so obvious that pointer addition works for arrays of objects other than 1-byte characters. Consider an array nArray of ints. Since an int occupies 4 bytes in Code::Blocks/gcc, if nArray is located at 0x1000, then nArray[5] will be located at 0x1000 + (5 * 4) or 0x1014.

remember.eps Hexadecimal 0x14 is equal to 20 decimal.

Fortunately for us, in C++, array + n points to array[n] no matter how large a single element of array might be. C++ makes the necessary conversions to ensure that this relationship is true.

Constant Nags

Chapter 14 introduced the concept of const variables. For example, the following

  const double PI = 3.14159;

declares a constant variable PI. Constant variables must be initialized when created and cannot be changed later, as with numbers like 2 and 3.14159.

The concept of const-ness can be applied to pointers as well, but the question is, where does the const keyword go? Consider the following three declarations. Which of these are legal?

  const char* pszArray1;
char const* pszArray2;
char* const pszArray3;

It turns out all three are legal, but one of them has a different meaning from the other two. The first two variables, pszArray1 and pszArray2, are both pointers to constant char arrays. This means you can modify the pointers, but you cannot modify the characters that they point at. Thus the following is legal:

  pszArray1 = new char[128];   // this is OK

The following, however, is not:

  (*pszArray1) = 'a';          // not legal

By comparison, pszArray3 is a constant pointer to a char array. In this case, you cannot change the pointer once it has been declared. Therefore you must initialize it when it’s declared (since you won’t get a chance to do that later), as in the following:

  char* const pszArray3 = new char[128];

Once the pointer is declared, the following is not legal:

  pszArray3 = pszArray1;       // not legal - you
                             // can't change pszArray3

But you can change the characters that it points to, like this:

  char* const pszArray3 = new char[128];
(*pszArray3) = 'a';          // legal

A single pointer can both be a constant and point to constant characters:

  const char* const pszMyName = "Stephen";

The value of this pointer cannot be changed, nor can the characters that it points to.

tip.eps As a beginning programmer, do you really need to worry about all these constant declarations? The answer is, “Sometimes.” You’ll get a warning if you do the following:

  char* pszMyName = "Stephen";

because you could conceivably try to modify my name by putting *pszMyName (or the equivalent pszMyName[n]) on the left-hand side of an assignment operator. The proper declaration is

  const char* pszMyName = "Stephen";

Differences Between Pointers and Arrays

With all the similarities, one might be tempted to turn the question around and ask, “What’s the difference between a pointer and the address of an array?” There are basically two differences:

  • An array allocates space for the objects; a pointer does not.
  • A pointer allocates space for the address; an array does not.

Consider these two declarations:

  int nArray[128];
int* pnPtr;

Both nArray and pnPtr are of type pointer to int, but nArray also allocates space for 128 int objects, whereas pnPtr does not. You can consider nArray to be a constant address in the same way that 3 is a constant int. You can no more put nArray on the left-hand side of an assignment than you can 3. The following is not allowed:

  nArray = pnPtr;  // not allowed

Thus pnPtr is of type int*, whereas nArray is actually of type int* const.

My main() Arguments

Now you’ve come far enough to learn the last secret of the program template that you’ve been using: What are the arguments to main()?

  int main(int nNumberOfArgs, char* pszArgs[])

These point to the arguments of the program. The first argument is the number of arguments to the program, including the name of the program itself. The second argument is an array of pointers to the ASCIIZ character strings that represent the arguments themselves. Arrays of pointers? What?

Arrays of pointers

If a pointer can point to an array, then it seems only fitting that the reverse should be true as well. Arrays of pointers are a type of array of particular interest.

The following declares an array of ten pointers to integers:

  int* pInt[10];

Given this declaration, then pInt[0] is a pointer to an integer. The following snippet declares an array of three pointers to integers and assigns them values:

  void fn()
{
    int n1, n2, n3;
    int* pInts[3] = {&n1, &n2, &n3};

    for(int n = 0; n < 3; n++)
    {
        // initialize the integers
        *pInts[n] = n * 10;
    }
}

After the declaration, pInts[0] points to the variable n1, pInts[1] points to n2, and pInts[2] points to n3. Thus an expression like

  *pInts[1] = 10;

sets the int pointed at by pInts[1] (that would be n2) to 10. The effect of the for loop in the prior snippet is to initialize n1, n2, and n3 to 0, 10, and 20, respectively. This is shown graphically in Figure 18-6.

9781118823873-fg1806.tif

Figure 18-6: The effects of setting up and using an array of three pointers to integers.

Arrays of arguments

Returning to the main() example, the arguments to the program are the strings that are passed to the program when it is executed. Thus, if I execute MyProgram as

  MyProgram file1 file2 /w

the arguments to the program are file1, file2, and /w.

Although technically not an argument, C++ includes the name of the program as the first “argument.”

technicalstuff.eps Switches are not interpreted, so /w is passed to the program as an argument. However, the special symbols <", "> and | are interpreted by the command line interpreter and are not passed to the program.

The following simple PrintArgs program displays the arguments passed to it by the command line interpreter:

  // PrintArgs - print the arguments to the program
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
    for(int n = 0; n < nNumberofArgs; n++)
    {
        cout << "Argument " << n
             << " is <" << pszArgs[n]
             << ">" << endl;
    }

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    cout << "Press Enter to continue..." << endl;
    cin.ignore(10, ' '),
    cin.get();
    return 0;
}

Now the trick is how to pass arguments to the program.

Passing arguments to your program through the command line

The easiest and most straightforward way is to simply type the arguments when executing the program from the command-line prompt:

  PrintArgs file1 file2 /w

Doing so generates the following output:

  C:Beginning_Programming-CPPPrintArgsinDebug>PrintArgs file1 file2 /w
Argument 0 is <printargs>
Argument 1 is <file1>
Argument 2 is <file2>
Argument 3 is </w>
Press Enter to continue …

The difficulty to this approach is knowing where the executable is stored. During the Build step, Code::Blocks creates the executable program in a subdirectory of the directory containing the project. Whether or not you used the default installation location shown in the preceding code, you can always find the project directory by selecting Project⇒Properties. The default Project Settings tab of the dialog box that pops up displays the path to the project file, as shown in Figure 18-7.

9781118823873-fg1807.tif

Figure 18-7: The Code::Blocks Project Settings tab of the Project/Target Options dialog box contains the path to the project file.

Select the Build Targets tab to find the path to the executable file, as shown in Figure 18-8.

If you’re using Windows, open an MS-DOS window by selecting Start⇒Programs⇒Accessories⇒Command Prompt. Navigate to the proper window by using the CD command (“CD” stands for Change Directory; it’s okay to use lowercase).

Using the directory path provided in Figure 18-7, I would enter the following:

  cd Beginning_Programming-CPPPrintArgsinDebug
PrintArgs file1 file2 /w

The details for Linux and Macintosh will be slightly different but similar.

9781118823873-fg1808.tif

Figure 18-8: The Build Targets tab indicates the name and location of the executable.

Passing arguments to your program from the Code::Blocks environment

You can pass arguments to your program from within Code::Blocks itself by selecting Project⇒Set Projects’ Arguments. This opens the dialog box shown in Figure 18-9. Enter the arguments into the Program Arguments entry field.

9781118823873-fg1809.tif

Figure 18-9: You can set up the project to pass arguments to the program when executed from Code::Blocks.

Executing the program from Code::Blocks opens a command-line window with the following contents:

  Argument 0 is <C:Beginning_Programming-CPPPrintArgsinDebugPrintArgs.exe>
Argument 1 is <file1>
Argument 2 is <file2>
Argument 3 is </w>
Press Enter to continue …

This technique is a lot easier, but it works only from within the Code::Blocks environment. However, this is the only way to pass arguments to your program when you’re using the Code::Blocks debugger. (I talk about the debugger in Chapter 20.)

Passing arguments to your program through Windows

In Windows, there is one final way of passing arguments to a program: Windows executes a program with no arguments if you double-click the name of the executable file. However, if you drag a set of files and drop them on the program’s executable filename, Windows executes the program, passing it the names of the files as its arguments.

To demonstrate, I created a couple of dummy files in the same directory as the PrintArg.exe file called file1.txt and file2.txt, as shown in Figure 18-10.

9781118823873-fg1810.tif

Figure 18-10: Here I created two dummy files in the same directory as the PrintArgs.exe executable.

I then selected both files and dragged and dropped them onto the PrintArgs.exe filename. Figure 18-11 shows the result.

9781118823873-fg1811.tif

Figure 18-11: Dropping the two filenames on the PrintArgs.exe filename instructs Windows to launch the program and pass the names of the files as arguments to the program.

warning.eps Windows does not pass the filenames to the program in any particular order. Specifically, it does not necessarily pass them in the same order in which they appear in the directory list — or the order in which you selected them.

remember.eps This chapter and its predecessor are not easy for a beginner. Don’t despair if you’re feeling a little uncertain right now. You may need to reread this section. Make sure that you understand the examples and the demonstration programs. You should find yourself growing more and more comfortable with the concept of pointer variables as you make your way through the remainder of the book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.182.66