Chapter 15. Simple Pointers

The choice of a point of view is the initial act of culture.

Ortega y Gasset

There are things, and there are pointers to things (Figure 15-1).

A thing and a pointer to a thing
Figure 15-1. A thing and a pointer to a thing

Things can come in any size; some may be big, some may be small. Pointers come in only one size (relatively small).

Throughout this book I use a box to represent a thing. The box may be large or small, but things are always a box. Pointers are represented by arrows.

Most novice programmers get pointers and their contents confused. To limit this problem, all pointer variables in this book end with the extension _ptr . You probably want to follow this convention in your own programs. Although not as common as it should be, this notation is extremely useful.

Figure 15-1 shows one thing: a variable named thing. The name of the variable is written on the box that represents it. This variable contains the value 6. The actual address of this variable is 0x1000. C++ automatically assigns an address to each variable at compile time. The actual addresses differ from machine to machine. Most of the time you don’t have to worry about variable addresses, as the compiler takes care of that detail. (After all, you’ve gotten through 14 chapters of programming without knowing anything about addresses.)

The pointer (thing_ptr) points to the variable thing. Pointers are also called address variables since they contain the addresses of other variables. In this case, the pointer contains the address 0x1000. Since this is the address of thing, you say that thing_ptr points to thing. (You could put another address in thing_ptr and force it to point to something else.)

You use “things” and “addresses” in everyday life. For example, you might live in a house (a thing). The street address might be “123 W. Main Street.” An address is a small thing that can be written down on a piece of paper. Putting a house on a piece of paper is something else, requiring a lot of work and a very large crane.

Street addresses are approximately the same size: one line. Houses come in various sizes. So while “1600 Pennsylvania Ave.” might refer to a big house and “8347 Skid Row” might refer to a one-room shack, both addresses are the same size.[1]

Many different address variables can point to the same thing. This is true for street addresses as well. Table 15-1 lists the location of important services in a small town.

Table 15-1. Small-town directory

Service (variable name)

Address (address value)

Building (thing)

Fire department

1 Main Street

City Hall

Police station

1 Main Street

City Hall

Planning office

1 Main Street

City Hall

Gas station

2 Main Street

Ed’s Gas Station

In this case you have one, large, multipurpose building that is used by several services. Although there are three address variables (Services), there is only one address (1 Main Street) pointing to one building (City Hall).

As you will see in this chapter, pointers can be used as a quick and simple way to access arrays. In later chapters you will discover how pointers can be used to create new variables and complex data structures, such as linked lists and trees. As you go through the rest of the book, you will be able to understand these data structures as well as create your own.

A pointer is declared by putting an asterisk (*) in front of the variable name in the declaration statement:

int thing;       // Define "thing" (see Figure 15-2A)
int *thing_ptr;  // Define "pointer to a thing" (see Figure 15-2A)

Table 15-2 lists the operators used in conjunction with pointers.

Table 15-2. Pointer operators

Operator

Meaning

*

Dereference (given a pointer, get the thing referenced)

&

Address of (given a thing, point to it)

The ampersand operator (&) changes a thing into a pointer. The * changes a pointer into a thing. These operators can easily cause confusion. Let’s look at some simple uses of these operators in detail:

thing

A thing. The declaration int thing does not contain an asterisk, so thing is not a pointer. For example:

       thing = 4;
&thing

A pointer to thing. thing is an object. The & (address of) operator gets the address of an object (a pointer), so &thing is a pointer. For example:

thing_ptr = &thing;	     // Point to the thing 
                             // (See Figure 15-2A)
*thing_ptr = 5;              // Set "thing" to 5 
                             // (See Figure 15-2C)
Pointer operators
Figure 15-2. Pointer operators
thing_ptr

Thing pointer. The asterisk (*) in the declaration indicates this is a pointer. Also, you have put the extension _ptr onto the name.

*thing_ptr

A thing. The variable thing_ptr is a pointer. The * (dereference operator) tells C++ to look at the data pointed to, not at the pointer itself. Note that this points to an integer, any integer. It may or may not point to the specific variable thing.

*thing_ptr = 5;              // Assign 5 to an integer
                             // We may or may not be pointing
                                to the specific integer "thing"

The following examples show misuse of pointer operators.

*thing

Illegal. Asks C++ to get the object pointed to by the variable thing. Since thing is not a pointer, this is an invalid operation.

&thing_ptr

Legal, but strange. thing_ptr is a pointer. The & (address of) operator gets a pointer to the object (in this case thing_ptr). The result is a pointer to a pointer. (Pointers to pointers do occur in more complex programs.)

Example 15-1 illustrates a very simple use of pointers. It declares one object, thing_var, and a pointer, thing_ptr. thing_var is set explicitly by the line:

    thing_var = 2;

The line:

    thing_ptr = &thing_var;

causes C++ to set thing_ptr to the address of thing_var. From this point on, thing_var and *thing_ptr are the same.

Example 15-1. thing/thing.cpp
#include <iostream>

int main(  )
{
    int   thing_var;  // define a variable 
    int  *thing_ptr;  // define a pointer 

    thing_var = 2;      // assigning a value to thing
    std::cout <<"Thing " << thing_var << '
';

    thing_ptr = &thing_var; // make the pointer point to thing
    *thing_ptr = 3;         // thing_ptr points to thing_var so
                            // thing_var changes to 3
    std::cout << "Thing " << thing_var << '
';

    // another way of printing the data
    std::cout << "Thing " << *thing_ptr << '
';
    return (0);
}

Several pointers can point to the same thing:

 1:      int      something;       
 2: 
 3:      int      *first_ptr;     // One pointer
 4:      int      *second_ptr;    // Another pointer
 5: 
 6:      something = 1;          // Give the thing a value
 7: 
 8:      first_ptr = &something; 
 9:      second_ptr = first_ptr;

In line 8 you use the & operator to change a simple variable (something) into a pointer that can be assigned to first_ptr. Because first_ptr and second_ptr are both pointers, you can do a direct assignment in line 9.

After executing this program fragment, you have the situation illustrated by Figure 15-3.

Two pointers and a thing
Figure 15-3. Two pointers and a thing

It is most important to note that while you have three variables, there is only one integer (thing). The following are all equivalent:

something = 1; 
*first_ptr = 1; 
*second_ptr = 1;

Finally, there is a special pointer called NULL that points to nothing. (The actual numeric value is 0.) The standard include file, stddef.h, defines the constant NULL. (Most standard include files that have anything to do with pointers automatically include NULL as well.) The NULL pointer is represented graphically in Figure 15-4.

NULL
Figure 15-4. NULL

const Pointers

Declaring constant pointers is a little tricky. For example, although the declaration:

const int result = 5;

tells C++ that result is a constant, so:

result = 10;      // Illegal

is illegal. The declaration:

const char *answer_ptr = "Forty-Two";

does not tell C++ that the variable answer_ptr is a constant. Instead it tells C++ that the data pointed to by answer_ptr is a constant. The data cannot be changed, but the pointer can. Again we need to make sure we know the difference between “things” and “pointers to things.”

What’s answer_ptr? A pointer. Can it be changed? Yes, it’s just a pointer. What does it point to? A const char array. Can the data pointed to by answer_ptr be changed? No, it’s constant.

Translating these rules into C++ syntax we get the following:

answer_ptr = "Fifty-One";     // Legal (answer_ptr is a variable)
*answer_ptr = 'X';            // Illegal (*answer_ptr is a constant)

If you put the const after the *, you tell C++ that the pointer is constant. For example:

char *const name_ptr = "Test";

What’s name_ptr? A constant pointer. Can it be changed? No. What does it point to? A character. Can the data we pointed to by name_ptr be changed? Yes.

name_ptr = "New";           // Illegal (name_ptr is constant)
*name_ptr = 'B';            // Legal (*name_ptr is a char)

Finally, we put const in both places, creating a pointer that cannot be changed to a data item that cannot be changed:

const char *const title_ptr = "Title";

One way of remembering whether the const modifies the pointer or the value is to remember that *const reads “constant pointer” in English.

Pointers and Printing

In C++ you can display the value of a pointer just like you can display the value of a simple variable such as an integer or floating point number. For example:

int an_integer = 5;           // A simple integer
int *int_ptr = &an_integer;   // Pointer to an integer

std::cout << "Integer pointer " << int_ptr << '
';

outputs

Integer pointer 0x58239A

In this case, the value 0x58239A represents a memory address. This address may vary from program to program.

C++ treats character pointers a little differently from other pointers. A character pointer is treated as a pointer to a C-style string. For example:

char some_characters[10] = "Hello";   // A simple set of characters
char *char_ptr = &some_characters[0];  // Pointer to a character

std::cout << "String pointer " << char_ptr << '
';

outputs

String pointer Hello

So with string pointers, the string itself is printed.

Pointers and Arrays

C++ allows pointer arithmetic. Addition and subtraction are allowed with pointers. Suppose you have the following:

char array[10]; 
char *array_ptr = &array[0];

This is represented graphically in Figure 15-5.

Pointers and an array
Figure 15-5. Pointers and an array

In this example, *array_ptr is the same as array[0], *(array_ptr+1) is the same as array[1], and so on. Note the use of parentheses. (*array_ptr)+1 is not the same as array[1]. The +1 is outside the parentheses, so it is added after the dereference. Thus (*array_ptr)+1 is the same as array[0]+1.

At first glance this may seem like a complex way of representing simple array indices. You are starting with simple pointer arithmetic. In later chapters you will use more complex pointers to handle more difficult functions efficiently.

Pointers are merely variables that contain memory addresses. In an array each element is assigned to consecutive addresses. For example, array[0] may be placed at address 0xff000024. Then array[1] would be placed at address 0xff000025 and so on. Example 15-3 prints out the elements and addresses of a simple character array. (Note: The I/O manipulators hex and dec are described in Chapter 16. The reinterpret_cast is discussed later in this chapter.)

Example 15-2. array-p/array-p.cpp
#include <cassert>
#include <iostream>
#include <iomanip>      

const int ARRAY_SIZE  = 10; // Number of characters in array 
// Array to print
char array[ARRAY_SIZE] = "012345678";   

int main(  )
{
    int index;  /* Index into the array */

    for (index = 0; index < ARRAY_SIZE; ++index) {
        std::cout << std::hex;  // Trick to print hex numbers
        assert(index >= 0);
        assert(index < sizeof(array)/sizeof(array[0]));
        std::cout << 
            "&array[index]=0x" <<  
                reinterpret_cast<int>(&array[index]) << 

            " (array+index)=0x" << 
                reinterpret_cast<int>(array+index) << 

            " array[index]=0x" <<  
                static_cast<int>(array[index]) << '
';
        std::cout << std::dec;  // Another trick to go back to decimal
    }
    return (0);
}

When run, this program prints:

&array[index]=0x20090 (array+index)=0x20090 array[index]=0x30 
&array[index]=0x20091 (array+index)=0x20091 array[index]=0x31 
&array[index]=0x20092 (array+index)=0x20092 array[index]=0x32 
&array[index]=0x20093 (array+index)=0x20093 array[index]=0x33 
&array[index]=0x20094 (array+index)=0x20094 array[index]=0x34 
&array[index]=0x20095 (array+index)=0x20095 array[index]=0x35 
&array[index]=0x20096 (array+index)=0x20096 array[index]=0x36 
&array[index]=0x20097 (array+index)=0x20097 array[index]=0x37 
&array[index]=0x20098 (array+index)=0x20098 array[index]=0x38 
&array[index]=0x20099 (array+index)=0x20099 array[index]=0x0

Characters usually take up one byte, so the elements in a character array will be assigned consecutive addresses. A short int takes up two bytes, so in an array of short ints, the addresses increase by two. Does this mean short_array+1 will not work for anything other than characters? No. C++ automatically scales pointer arithmetic so it works correctly. In this case short_array+1 will point to element number 1.

C++ provides a shorthand for dealing with arrays. Rather than write:

array_ptr = &array[0];

you can write:

array_ptr = array;

C++ blurs the distinction between pointers and arrays by treating them the same in many cases. Here you used the variable array as a pointer, and C++ automatically did the necessary conversion.

Example 15-3 counts the number of elements that are nonzero and stops when a zero is found. No limit check is provided, so there must be at least one zero in the array.

Example 15-3. ptr2/ptr2a.cpp
#include <cassert>
#include <iostream>

int array[10] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3};
int the_index;

int main(  )
{
    the_index = 0;
    while (true) {
        assert(the_index >= 0);
        assert(the_index < sizeof(array)/sizeof(array[0]));

        if (array[the_index] == 0)
            break;

        ++the_index;
    }

    std::cout << "Number of elements before zero " << the_index << '
';
    return (0);
}

Rewriting this program to use pointers gives us Example 15-4.

Example 15-4. ptr2/ptr2.cpp
#include <iostream>

int array[10] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3};
int *array_ptr;

int main(  )
{
    array_ptr = array;

    while ((*array_ptr) != 0)
        ++array_ptr;

    std::cout << "Number of elements before zero " << 
        (array_ptr - array) << '
';
    return (0);
}

Tip

In the second example, we lost the safety provided by the assert statement.

The first program uses the expression (array[index] != 0). This requires the compiler to generate an index operation, which takes longer than a simple pointer dereference: ((*array_ptr) != 0). The expression at the end of this program, array_ptr -array, computes how far array_ptr is into the array.

When passing an array to a procedure, C++ will automatically change the array into a pointer. In fact, if you put an & before the array, C++ will issue a warning. Example 15-5 illustrates array passing.

Example 15-5. init-a/init-a.cpp
#include <cassert>

const int MAX = 10;

/********************************************************
 * init_array_1 -- Zero out an array                    *
 *                                                      *
 * Parameters                                           *
 *      data -- the array to zero                       *
 ********************************************************/
void init_array_1(int data[])
{
    int  index;

    for (index = 0; index < MAX; ++index) {
        assert(index >= 0);
        assert(index < MAX);
        data[index] = 0;
}

/********************************************************
 * init_array_2 -- Zero out an array                    *
 *                                                      *
 * Parameters                                           *
 *      data_ptr -- pointer to array to zero            *
 ********************************************************/
void init_array_2(int *data_ptr)
{
    int index;

    for (index = 0; index < MAX; ++index)
        *(data_ptr + index) = 0;
}
int main(  )
{
    int  array[MAX];

    // one way of initializing the array
    init_array_1(array);

    // another way of initializing the array
    init_array_1(&array[0]);

    // Similar to the first method but
    //    function is different
    init_array_2(array);

    return (0);
}

Splitting a C-Style String

Suppose you are given a C-style string (character array) of the form “Last/First”. You want to split this into two strings, one containing the first name and one containing the last name.

Example 15-6 reads in a single line, stripping the newline character from it. The function strchr is called to find the location of the slash (/). (The function strchr is actually a standard function. I have duplicated it for this example so you can see how it works.)

At this point last_ptr points to the beginning character of the last name (with the first tacked on) and first_ptr points to a slash. You then split the string by replacing the slash (/) with an end-of-string (NUL or “”). Now last_ptr points to just the last name and first_ptr points to a null string. Moving first_ptr to the next character makes first_ptr point to the beginning of the first name.

Graphically what you are doing is illustrated in Figure 15-6. Example 15-6 contains the full program.

Splitting a string
Figure 15-6. Splitting a string
Example 15-6. split/split.cpp
/********************************************************
 * split -- split a entry of the form Last/First        *
 *      into two parts.                                 *
 ********************************************************/
#include <iostream>
#include <cstring>
#include <cstdlib>

int main(  )
{
    char line[80];      // The input line
    char *first_ptr;    // pointer we set to point to the first name
    char *last_ptr;     // pointer we set to point to the last name 

    std::cin.getline(line, sizeof(line));

    last_ptr = line;    // last name is at beginning of line 

    first_ptr = strchr(line, '/'),      // Find slash 

    // Check for an error 
    if (first_ptr == NULL) {
        std::cerr << "Error: Unable to find slash in " << line << '
';
        exit (8);
    }

    *first_ptr = '';  // Zero out the slash 

    ++first_ptr;        // Move to first character of name 

    std::cout << "First:" << first_ptr << " Last:" << last_ptr << '
';
    return (0);
}
/********************************************************
 * strchr -- find a character in a string               *
 *      Duplicate of a standard library function,       *
 *      put here for illustrative purposes.             *
 *                                                      *
 * Parameters                                           *
 *      string_ptr -- string to look through            *
 *      find -- character to find                       *
 *                                                      *
 * Returns                                              *
 *      pointer to 1st occurrence of character in string*
 *      or NULL for error                               *
 ********************************************************/
char *strchr(char * string_ptr, char find)
{
    while (*string_ptr != find) {

       // Check for end 

       if (*string_ptr == '')
           return (NULL);       // not found 

        ++string_ptr;
    }
    return (string_ptr);        // Found 
}

This program illustrates how pointers and character arrays may be used for simple string processing.

Question 15-1: Example 15-7 is supposed to print out:

Name: tmp1

but instead you get:

Name: !_@$#ds80

(Your results may vary.) Why does this happen? Would this happen if we used the C++ string class instead of the old C-style strings?

Example 15-7. tmp-name/tmp-name.cpp
#include <iostream>
#include <cstring>

/********************************************************
 * tmp_name -- return a temporary file name             *
 *                                                      *
 * Each time this function is called, a new name will   *
 * be returned.                                         *
 *                                                      *
 * Returns                                              *
 *      Pointer to the new file name.                   *
 ********************************************************/
char *tmp_name(  )
{
    char name[30];              // The name we are generating 
    static int sequence = 0;    // Sequence number for last digit 

    ++sequence; // Move to the next file name 

    strcpy(name, "tmp");

    // Put in the sequence digit 
    name[3] = static_cast<char>(sequence + '0'),

    // End the string 
    name[4] = '';

    return(name);
}

int main(  )
{
    std::cout << "Name: " << tmp_name(  ) << '
';
    return(0);
}

The reinterpret_cast

The reinterpret_cast is used to tell C++ to interpret a value as a different type. It is used to convert pointers to integers, integers to pointers, and pointers from one type to another. The syntax is:

reintepret_cast<type>(expression)

where type is of the result of the conversion.

Pointers and Structures

In Chapter 12, you defined a structure for a mailing list:

struct mailing { 
    std::string   name;    // Last name, first name 
    std::string   address1;// Two lines of street address 
    std::string   address2; 
    std::string   city;      
    char     state[2];     // Two-character abbreviation[2]
    long int zip;          // Numeric zip code
} list[MAX_ENTRIES];

Mailing lists must frequently be sorted in name order and Zip-code order. You could sort the entries themselves, but each entry is 226 bytes long. That’s a lot of data to move around. A way around this problem is to declare an array of pointers and then sort the pointers:

// Pointer to the data
struct mailing *list_ptrs[MAX_ENTRIES];  

int current;    // Current mailing list entry

    // ....

    for (current = 0; current < number_of_entries; ++current) {
    list_ptrs[current] = &list[current]; }

    // Sort list_ptrs by zip code

Now instead of having to move a large structure around, you are moving 4-byte pointers. This sorting is much faster. Imagine that you had a warehouse full of big heavy boxes and you needed to locate any box quickly. One way of doing this would be to put the boxes in alphabetical order. But that would require a lot of moving, so you assign each location a number, write down the name and number on index cards, and sort the cards by name.

Command-Line Arguments

The procedure main actually takes two arguments. They are called argc and argv. (They don’t have to be called argc and argv; however, 99.99% of C++ programs use these names.)

int main(int argc, char *argv[]) 
{

It’s easy to remember which comes first when you realize that they are in alphabetical order.

The parameter argc is the number of arguments on the command line (including the program name). The array argv contains the actual arguments. For example, if the program args were run with the command line:

args this is a test

then:

argc       = 5 
argv[0]       = "args" 
argv[1]       = "this" 
argv[2]       = "is" 
argv[3]       = "a" 
argv[4]       = "test"

Note

The Unix shell expands wildcard characters like *, ?, and [] before sending the command line to the program. See your sh or csh manual for details.

Borland-C++ will expand wildcard characters if the file WILDARG.OBJ is linked with your program. See the Borland-C++ manual for details.

Almost all Unix commands use a standard command-line format. This “standard” has carried over into other environments. A standard UNIX command has the form:

command options file1 file1 file3 ...

Options are preceded by a hyphen (-) and are usually a single letter. For example, the option -v might turn on verbose mode. If the option takes a parameter, the parameter follows the letter. For example, the switch -- m1024 sets the maximum number of symbols to 1024, and -- ooutfile sets the output file name to outfile.

You have been given the assignment to write a program that will format and print files. Part of the documentation for the program looks like:

print_file [-v] [-l<length>] [-o<name>] [file1] [file2] ...

In this line, -v sets verbose options, which turns on a lot of progress information messages. The option -l<length> sets page size to <length> lines (default = 66), and -o<name> sets the output file to <name> (default = print.out). A list of files to print follows these options ([file1], [file2], etc.). If no files are specified, print the file print.in.

The while loop cycles through the options. The actual loop is:

    while ((argc > 1) && (argv[1][0] == '-')) {

There is always one argument, the program name. The expression (argc > 1) checks for additional arguments. The first one will be numbered 1. The first character of the first argument is argv[1][0]. If this character is a dash, you have an option.

At the end of the loop is the code:

        --argc; 
        ++argv; 
    }

This consumes an argument. The number of arguments is decremented to indicate one fewer option, and the pointer to the first option is incremented, shifting the list to the left one place. (Note that after the first increment, argv[0] no longer points to the program name.)

The switch statement is used to decode the options. Character 0 of the argument is the hyphen (-). Character 1 is the option character, so you use the following expression to decode the option:

switch (argv[1][1]) {

The option -v has no arguments; it just causes a flag to be set.

The -l option takes an integer argument. The library function atoi is used to convert the string into an integer. From the previous example, you know that argv[1][2] starts the string containing the number. This string is passed to atoi.

The option -o takes a filename. Rather than copy the whole string, you set the character pointer out_file to point to the name part of the string. By this time you know that:

argv[1][0] = '-'
argv[1][1] = ‘o’
argv[1][2] = first character of the file name

You set out_file to point to the string with the statement:

out_file = &argv[1][2];

Finally all the options are parsed, and you fall through to the processing loop. This merely executes the function do_file for each file argument.

Example 15-8 contains the complete option-decoding program.

Example 15-8. print/print.cpp
/********************************************************
 * print -- format files for printing                   *
 ********************************************************/
#include <iostream>
#include <cstdlib>      

int verbose = 0;                // verbose mode (default = false) 
char *out_file = "print.out";   // output file name 
char *program_name;             // name of the program (for errors) 
int line_max = 66;              // number of lines per page 

/********************************************************
 * do_file -- dummy routine to handle a file            *
 *                                                      *
 * Parameter                                            *
 *      name -- name of the file to print               *
 ********************************************************/
void do_file(const char *const name)
{
    std::cout << "Verbose " << verbose << " Lines " << line_max << 
            " Input " << name << " Output " << out_file << '
';
}
/********************************************************
 * usage -- tell the user how to use this program and   *
 *              exit                                    *
 ********************************************************/
void usage(  )
{
    std::cerr << "Usage is " << program_name << 
        " [options] [file-list]
";
    std::cerr << "Options
";
    std::cerr << "  -v          verbose
";
    std::cerr << "  -l<number>  Number of lines
";
    std::cerr << "  -o<name>    Set output file name
";
    exit (8);
}

int main(int argc, char *argv[])
{
    // save the program name for future use 
    program_name = argv[0];

    /* 
     * loop for each option.  
     *   Stop if we run out of arguments
     *   or we get an argument without a dash.
     */
    while ((argc > 1) && (argv[1][0] == '-')) {
        /*
         * argv[1][1] is the actual option character.
         */
        switch (argv[1][1]) {
            /*
             * -v verbose 
             */
            case 'v':
                verbose = 1; 
                break;
            /*
             * -o<name>  output file
             *    [0] is the dash
             *    [1] is the "o"
             *    [2] starts the name
             */
            case 'o':
                out_file = &argv[1][2];
                break;
            /*
             * -l<number> set max number of lines
             */
            case 'l':
                line_max = atoi(&argv[1][2]);
                break;
            default:
                std::cerr << "Bad option " << argv[1] <<'
';
                usage(  );
        }
        /*
         * move the argument list up one
         * move the count down one
         */
        ++argv;
        --argc;
    }

    /*
     * At this point all the options have been processed.
     * Check to see if we have no files in the list
     * and if so, we need to list just standard in.
     */
    if (argc == 1) {
        do_file("print.in");
    } else {
        while (argc > 1) {
          do_file(argv[1]);
          ++argv;
          --argc;
        }
    }
    return (0);
}

This is one way of parsing the argument list. The use of the while loop and switch statement is simple and easy to understand. This method does have a limitation. The argument must immediately follow the options. For example, -odata.out will work, but -o data.out will not. An improved parser would make the program more friendly, but this works for simple programs. (See your system documentation for information on the getopt function.)

Programming Exercises

Exercise 15-1: Write a program that uses pointers to set each element of an array to zero.

Exercise 15-2: Write a function that takes a single C-style string as its argument and returns a pointer to the first nonwhitespace character in the string.

Answers to Chapter Questions

Answer 15-1: The problem is that the variable name is a temporary variable. The compiler allocates space for the name when the function is entered and reclaims the space when the function exits. The function assigns name the correct value and returns a pointer to it. However, the function is over, so name disappears and you have a pointer with an illegal value.

The solution is to declare name static. Consequently, it is a permanent variable and will not disappear at the end of the function.

If we had used the C++ string class we would not have had this problem. That’s because the string class’s copy constructor and memory allocation logic prevents the destruction of memory before we finish using it.

Question 15-2: After fixing the function, you try using it for two filenames. Example 15-9 should print out:

Name: tmp1 
Name: tmp2

but it doesn’t. What does it print and why?

Example 15-9. tmp2/tmp2.cpp
#include <iostream>
#include <cstring>

/********************************************************
 * tmp_name -- return a temporary file name             *
 *                                                      *
 * Each time this function is called, a new name will   *
 * be returned.                                         *
 *                                                      *
 * Warning: There should be a warning here, but if we   *
 *      put it in we would answer the question.         *
 *                                                      *
 * Returns                                              *
 *      Pointer to the new file name.                   *
 ********************************************************/
char *tmp_name(  )
{
    static char name[30];       // The name we are generating
    static int sequence = 0;    // Sequence number for last digit 

    ++sequence; // Move to the next file name

    strcpy(name, "tmp");

    // But in the squence digit 
    name[3] = static_cast<char>(sequence + '0'),

    // End the string 
    name[4] = '';

    return(name);
}

int main(  )
{
    char *name1;                // name of a temporary file
    char *name2;                // name of a temporary file 

    name1 = tmp_name(  );
    name2 = tmp_name(  );

    std::cout <<"Name1: " << name1 << '
';
    std::cout <<"Name2: " << name2 << '
';
    return(0);
}

The first call to tmp_name returns a pointer to name. There is only one name. The second call to tmp_name changes name and returns a pointer to it. So you have two pointers, and they point to the same thing, name.

Several library functions return pointers to static strings. A second call to one of these routines will overwrite the first value. A solution to this problem is to copy the values:

char name1[100]; 
char name2[100]; 

strncpy(name1, tmp_name(  ), sizeof(name1)); 
strncpy(name2, tmp_name(  ), sizeof(name1));

OK, we decided to finally give up on C-style strings and enter the 21st century. So we’ve rewritten our function to use the C++ string class. Yet it still doesn’t work. Why?

Example 15-10. tmp3/tmp3.cpp
#include <iostream>
#include <string>

/********************************************************
 * tmp_name -- return a temporary file name             *
 *                                                      *
 * Each time this function is called, a new name will   *
 * be returned.                                         *
 *                                                      *
 * Returns                                              *
 *      String containing the name.                     *
 ********************************************************/
std::string& tmp_name(  )
{
    std::string name;   // The name we are generating
    static int sequence = 0;    // Sequence number for last digit 

    ++sequence; // Move to the next file name

    name = "tmp";

    // Put in the squence digit 
    name += static_cast<char>(sequence + '0'),

    return(name);
}

int main(  )
{
    std::string name1 = tmp_name(  );

    std::cout <<"Name1: " << name1 << '
';
    return(0);
}

The function returns a reference to name, which is a local variable. The problem is that C++ destroys the variable name at the end of the function. It then returns a reference to the variable (which just got destroyed), and that’s what’s assigned to name1.

You should never return references to local variables or parameters unless they are declared static.



[1] For readers not familiar with American culture, 1600 Pennsylvania Ave. is the address of the President’s mansion, while 8347 Skid Row is the typical address of a very poor family.

[2] Every once in a while, someone will send in a bug report stating that the size of the character array should be 3: two for the state abbreviation and one for the end of string character. In this example, the state is a character array, not a C-style string. We know that it contains two and only two characters, so we can represent it as two-character array.

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

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