CHAPTER 12

Pointers and
Dynamic Memory
Allocation

Chapter Objectives

By the end of the chapter, readers will be able to:

images  Explain the concepts of pointers.

images  Explain and use indirect addressing.

images  Declare, initialize, and use pointers, with up to three levels of indirection.

images  Pass parameters by pointer.

images  Use pointer arithmetic in the manipulation of pointers.

images  Use various predefined cString functions.

images  Perform dynamic memory allocation and deallocation.

images  Define and use ragged arrays.

images  Use dynamic single- and two-dimensional arrays.

images  Use memory-leak detection tools.

images  Define, use, and initialize function pointers.

Introduction

This chapter explores one of the most powerful tools in C and C++ programming. Unfortunately, it is also one of the more difficult topics to master. Pointers allow us to increase the complexity of our solutions unlike anything we have discussed up till now. It is especially important that you read this chapter's text carefully and do all of the exercises.

12.1 Definition

A pointer is a variable that holds an address. Although we learned in Chapter 10 that the name of an array references the array's beginning address, array names are not pointers. The address referenced by the name of an array is a constant and therefore can't be changed. Bottom line, a pointer is a variable that holds an address. Therefore, four bytes of memory is allocated for the pointer, assuming a 32-bit operating system. This is because addresses are always four bytes regardless of what they point to.

It may not be clear at this point why pointers are so powerful. However, pointers play an extremely important role in increasing the efficiency of our programs in many different ways. Arrays become more flexible, memory access is enhanced and expanded, and the creation of dynamic data structures becomes a possibility through the use of pointers.

Before we get to the more powerful aspects of pointers, we need to discuss the details of how pointers work.

Section 12.1 Exercises

1.  A pointer:

a. increases the efficiency of our programs.

b. is a variable.

c. holds an address.

d. all of the above.

2.  True or false: The name of an array is a pointer.

12.2 Declaration

Like any other variable, pointers need to be declared. The basic syntax is as follows:

images

The target type in the above syntax is any data type. The asterisk indicates that the variable will hold an address of the specified target type. Example 12.2.1 shows several pointer declarations.

images

The third declaration in Example 12.2.1 initializes the pointer to zero. The value 0 in this context is commonly referred to as null. This is done to give the pointer a known starting value. Otherwise, the address in the pointer will be unknown and more than likely invalid. From our discussion of cStrings you learned that there is an ASCII value 0 that is referred to as the null character (‘’). Make sure you use the null character to terminate cStrings and the null pointer to initialize pointers. There is a third method you may encounter that is used to initialize a pointer. This involves using a predefined value, NULL, that is a holdover from C and should never be used in C++. This incorrect C++ initialization of pointers is shown in Example 12.2.2.

images

There is a proposal put forth by Bjarne Stroustrup—the father of C++—and Herb Sutter—chair of the ISO C++ Standards Committee—to replace the value 0 for the initialization of pointers with the reserved word nullptr. Although most compilers don't currently support this, be aware that this may be available, and may perhaps be the standard, in the near future. As the authors of this text, as well as professors, we heartily embrace this proposed addition to the C++ language. The proposed syntax is shown in Example 12.2.3.

images

Declaring more than one pointer in the same line of code often causes some confusion, as shown in Example 12.2.4.

images

Example 12.2.4 does not declare two pointers. Instead, we have declared a character pointer called str1 and a character variable called str2. This is more than a little misleading for many students. Example 12.2.5 shows the correct method of declaring two pointers in the same statement.

images

Even though we can now declare pointers, we still can't do much with them. We can't store data in them because they hold addresses, not data. Somehow we need to get a valid address into the pointer.

At this point there are two ways to assign addresses to a pointer. The first is to assign something that is already an address to the pointer. We know from previous chapters that the name of an array references the array's beginning address. Therefore, we can assign that address to a pointer, as shown in Example 12.2.6.

images

Figure 12.2.1 Memory layout of pointers

images

Figure 12.2.1 shows the memory layout after the declaration of these variables.

As you can see, the pointers hold the addresses of the first elements of the arrays. You can also see how pointers get their name. They point to, or reference, another piece of memory.

The second way to retrieve an address to store in a pointer is by using the address-of operator (&). The address-of operator returns the address of its operand, which can be stored in a pointer, as shown in Example 12.2.7.

images

images

The third pointer declaration in Example 12.2.7, demonstrates another way to retrieve the beginning address of an array. Since numbers[0] is the first element of the array, the address of operator returns the address-of this element.

STYLE NOTE The spacing between the asterisk and the data type and pointer name is often debated. Some programmers believe that the asterisk should be next to the pointer name, as follows:

images

However, other programmers feel strongly that the asterisk should be next to the data type:

images

It is our preferred style to put a space on either side of the asterisk to ensure that it stands out from both the data type and the pointer name. Whatever your preference, choose a style and stick with it; consistency is what's important. In addition, don't forget to check the style guidelines for your company or school and conform to their standards.

Section 12.2 Exercises

1.  The ____________ of the pointer must match the ___________ of the value stored at that address.

2.  Explain the following statement:

images

3.  Please declare two integer pointers with appropriate initialization in the same statement.

4.  Please indicate whether the following statements are valid or invalid. If a statement is invalid, please state why and correct the errors.

images

images

5.  With reference to the following statement, explain the statements that follow:

images

12.3 Advanced Pointer Declarations

Since we can have a pointer to any data type, we can have a pointer that contains the address of yet another pointer. Now the fun starts! (Of course our students have always thought we had a warped definition of fun.) Example 12.3.1 demonstrates the declaration and initialization of a pointer to a pointer.

images

Typically, programmers will call this a two-star pointer. The variable flt_ptr_ptr will hold the address of an address that contains a float. Figure 12.3.1 illustrates the memory layout corresponding to Example 12.3.1.

You can have as many stars, or levels of indirection, as needed. It is likely that the most levels of indirection you will ever see, or use, are three- or four-star pointers—and even those very rarely and in very special situations.

images

Figure 12.3.1 Memory layout of a pointer to a pointer

Although it seems logical that we can assign the address of a two-dimensional array to a pointer, it is syntactically incorrect. Remember that a two-dimensional array is an array of arrays, therefore, a single-star pointer will not work. However, a two-star pointer will not work either, even though it may seem like it should. Example 12.3.2 shows some invalid and valid pointer assignments.

images

Notice that the third example in Example 12.3.2 is valid because we are accessing a row in the two-dimensional array. Since the row references an address of an integer, we can store that address in a pointer.

So now we have an address stored in the pointer. We still can't do much, but hang in there—we still have more to learn.

12.4 Indirection Operator

If we use a pointer on the left side of an assignment operator, we change the address stored in the pointer. To retrieve or alter the value that the pointer is pointing to, we use the indirection operator (*). The indirection operator removes one level of indirection. Using the indirection operator to acquire the value being referred to by the pointer is called dereferencing. This process is illustrated in Figure 12.4.1.

images

Figure 12.4.1 Dereferencing

In Figure 12.4.1, the first step declares a pointer and a character variable. As shown, the character pointer, ptr, is initialized to null, and the character variable, ch, to ‘A’. The second step uses the address-of operator to assign the address of ch, replacing the contents of the pointer ptr. Step 3 demonstrates the process of dereferencing the pointer. Once dereferenced, we replace the value ‘A’ with the character ‘B’. Example 12.4.1 also demonstrates the use and effect of the indirection operator.

images

images

As you can see from Example 12.4.1, it is possible to print addresses. However, there is no good reason to do this except for debugging purposes.

As stated previously, the indirection operator removes one level of indirection. If we have a two-star pointer—that is, two levels of indirection—dereferencing once gets us to the first address and dereferencing a second time allows us to access the value. Example 12.4.2 demonstrates this process.

images

images

We can now declare a pointer, assign an address to a pointer, and even manipulate what the pointer is pointing at. But just you wait—things are about to get a little more interesting.

Section 12.4 Exercises

1.  Explain what is meant by a two-star pointer.

2.  Explain the term “indirection.”

3.  What is the indirection operator, and what does it do?

4.  Explain what is happening in the following code:

images

5.  Draw the diagram that represents the memory corresponding to Example 12.4.2.

Section 12.4 Learn by Doing Exercises

1.  Use the following code to implement the exercises required in this section. Write a program that includes all of the following statements. Label each part in your output.

images

a. Write the statement that prints the address of the array using the array name.

b. Write the statement that prints the address of the array using int_ptr.

c. Write the statement that prints the address of the array using int_ptr_ptr.

d. Write the statement that prints the address of int_ptr using int_ptr.

e. Write the statement that prints the address of int_ptr using int_ptr_ptr.

f. Write the statement that prints the first element of the array using int_ptr.

g. Write the statement that prints the first element of the array using int_ptr_ptr.

h. Write the statement that changes int_ptr to point to the second element of the array.

i. Write the statement that prints the second element of the array using int_ptr.

j. Write the statement that prints the second element of the array using int_ptr_ptr.

k. Write the statement that changes the second element of the array to 101 using int_ptr.

l. Write the statement that changes the second element of the array to 102 using int_ptr_ptr.

m. Change the address in int_ptr so that it points to the third element of the array using int_ptr_ptr.

n. Write the statement that changes the third element of the array to – 1 using int_ptr.

o. Write the statement that changes the third element of the array to – 2 using int_ptr_ptr.

p. Print the contents of the array to the screen to verify that the contents of the array were changed.

12.5 Passing by Pointer

In Chapter 10 we learned that arrays are, by default, passed by pointer. This is because we pass the address of the array. Now we can learn how to pass by pointer variables other than arrays. This topic is especially important for those of us learning C. Passing by pointer is the only way C programmers can pass variables so that they can be permanently changed within a function. In fact, what C++ programmers call passing by pointer, C programmers call passing by reference.

Remember, any time we have the address of a memory location, we can change what is stored at that address. This is the reason passing by pointer allows us to change a value within a function. Example 12.5.1 demonstrates a parameter being passed by pointer.

images

images

images

Figure 12.5.1 Memory layout of passing by pointer

The function call in Example 12.5.1 passes the two addresses of integers; therefore, we need to catch these addresses in variables that can hold addresses: pointers. Figure 12.5.1 illustrates what is happening under the hood in Example 12.5.1.

What if we need to pass those pointers to another function from within GetFraction? This is not a problem, but it is an area where many of our students get confused. You do not need to pass the address of the pointers in GetFraction. Since you already have the addresses of the variables declared in main, you are still able to affect these original variables. Example 12.5.2 shows the proper way to manipulate these pointers. As this example is fairly long, make sure to look carefully at the parameter passing. Running this example, more than any others, is crucial in helping you understand the demonstrated process.

images

images

Notice the function call to FormatNegative in Example 12.5.2 passes only the pointer, not the address of the pointer. Also notice that the call to DisplayFraction from within GetFraction dereferences both pointers before passing them. Therefore, DisplayFraction just catches a copy of the two integers. Figure 12.5.2 graphically illustrates what is happening here.

Figure 12.5.2 shows that the pointers, numer and denom, in GetFraction were actually passed by value to FormatNegative. But since the r-value of the parameters are the addresses of the original variables declared in main, you can still change these variables from within FormatNegative.

images

Figure 12.5.2 Memory layout of passing by pointer

By now the C programmers among us are pretty excited about being able to finally pass by reference. However, the C++ programmers are wondering why we took a pretty simple topic and muddied it up. There is, of course, a reason for this, but you will need to wait just a little bit longer to find out what it is.

Section 12.5 Exercises

1.  Explain the difference between passing by reference and passing by pointer.

2.  True or false: In passing by pointer, you need to place an ampersand in front of the parameter in the call.

Section 12.5 Learn by Doing Exercise

1.  Convert the program shown in Example 9.6.1 so that only passing by value and passing by pointer are used.

12.6 Pointer Arithmetic

In all of our examples in this chapter, we obviously made up the addresses. However, our addresses do have one thing in common with the real thing: they're all numbers. The size of an address depends upon the operating system. In a 32-bit operating system, the address is 32 bits.

Since addresses are numbers, we can perform arithmetic operations on them. But why would we want to perform math on pointers? One reason is that if we have a pointer to an array, we can efficiently iterate through the array using the pointer. Example 12.6.1 demonstrates this process.

images

As you can see, the increment operator moves the pointer to the next element. One of the misconceptions is that if we have an array of integers, each of which is four bytes, we would have to increment the pointer four times to make sure we are at the start of the next element. This is unnecessary because pointer arithmetic takes into account the size of the target type of the pointer and increments appropriately. Example 12.6.2 demonstrates this feature of pointers.

images

We can use any mathematical operators we want; however, you should be careful not to go to the extreme, for you might access memory that you don't have the right to, causing your program to crash. The bottom line is to manage your pointers carefully to ensure that they maintain an appropriate address. Example 12.6.3 shows some additional pointer math.

images

As you can see from Example 12.6.3, using pointer arithmetic causes the code to become more difficult to read and understand. The first example is fairly straightforward and very commonly used. The indirection operator dereferences the pointer to access the first element, which is then printed. The address in ptr is then incremented to the next element. This process is repeated two more times, as indicated by the loop.

The second example dereferences the pointer, which is currently still pointing at the fourth element, value 3. The value is then decremented from 3 to 2, which is then displayed. Finally, the pointer is incremented to point to the next element. This process continues until the condition of the loop is false. We are not stuck using the increment and decrement operators either. Example 12.6.4 shows some additional pointer manipulations.

images

images

In the first example, since ptr holds the address of a character, cout will print from that address and will continue until it reaches a null character. It behaves exactly as if we had supplied the name of the array to cout. The second example adds 2 to the beginning address, therefore, ptr points to the character “t”. This address is dereferenced, so cout prints that specific character. The third example changes the pointer to point to the address of the fourth character. The cout statement then prints starting from that address until the null character. Notice that the address in ptr physically changed, so that when we print once again, we'll get the same output.

One thing that we have not discussed is using the subscript operator ([]) with pointers. Both the subscript operator and the indirection operator dereference an address. All four of the cout statements in Example 12.6.5 will produce the same output.

images

You can use the subscript operator and the indirection operator interchangeably. Although pointer notation is rarely used with arrays, array notation is often used with pointers.

One last thing regarding pointer arithmetic: Do not try to increment the address of the array. The name of an array references the beginning address of the array; however, the address is a constant address and therefore can't be changed. Attempting to use the increment operator with an array name will result in a syntax error.

Section 12.6 Exercises

1.  Given the following code fragments, determine the output.

images

Section 12.6 Learn by Doing Exercise

1.  Write a program that declares a two-dimensional array of integers with four rows and five columns. Initialize the array to any numbers of your choosing. Using a pointer, display the contents of the array.

12.7 Pointers Applied

The following examples demonstrate the power and elegance of pointers when applied to real-world situations. We strongly suggest that you examine each function carefully and make appropriate drawings, similar to the figures provided in this chapter, to aid in your understanding. Then run the code using your debugger to confirm your findings. If you don't understand the code, reread the chapter and work through all of the exercises.

Example 12.7.1 deletes a portion of a string. This function takes a cString (str), a starting array index (start), and an integer (num_char) representing the number of characters to delete from the supplied starting position.

images

Example 12.7.1 illustrates how using pointers can make your code much more compact. This example makes good use of a predefined function and some simple pointer arithmetic. Example 12.7.2 is probably not as easy to understand in terms of what is happening under the hood, even if the name of the function indicates its purpose.

images

Like the predefined strcpy, the function in Example 12.7.2 copies the characters from src to dest. There are a couple of things to point out in the code. Notice the semicolon at the end of the while loop. This would usually mean the loop would be infinite; however, because we use the assignment operator within the loop's condition, we are actually changing the control variable within the loop. Since the null character equates to zero, and zero represents false, as soon as the null is copied from src to dest, the loop ends.

The function in Example 12.7.3 illustrates one way of finding the first instance of the character (ch) in the cString (str). The function then returns the index of the specified character or −1 if the character is not found.

images

images

Example 12.7.3 demonstrates one feature of pointers that should be fairly obvious. Since addresses are numbers, we can subtract two addresses. This concept, coupled with the knowledge that arrays are contiguous blocks of memory, is what makes this function work.

As you can see from the previous functions, pointers can make your code fairly efficient, compact, and even elegant. Understanding what is happening at a very low level, or under the hood, is extremely important when using pointers.

Section 12.7 Exercise

1.  What is the purpose of the following function?

images

Section 12.7 Learn by Doing Exercise

1.  Write a program that includes a function called StrCat, which simulates its predefined namesake. This function receives two values, a pointer to the source (src), and a pointer to the destination (dest) cString. Make sure your destination parameter is large enough to accommodate the additional characters by creating your own version of strlen using pointer notation only.

12.8 More cString Functions

In Chapter 10 we spent a considerable amount of time discussing various predefined functions for manipulating cStrings. However, because the concept of pointers had not yet been introduced, we decided to hold off on presenting three additional functions: strtok, strstr, and strchr. Now that you have become familiar with pointers, you should find these functions relatively easy to understand. Note: All three of the functions return a character pointer and require the header file <cstring>.

In addition, there are a series of predefined functions, usually considered string handling functions, that actually work with blocks of memory of any type. A function that is representative of these is memset. Other related functions are memcpy, memcmp, memmove, and memchr. The memset function will be used to demonstrate how this category of functions works.

12.8.1 The strtok Function

The strtok function is used to separate a cString into pieces, or chunks, of information based on a specific delimiter. This process is also referred to as tokenizing a string.

The syntax for the strtok function is as follows:

char * strtok ( const char * str, const char * delimiter );

The first parameter is a null-terminated cString containing the token(s). A token is simply a sequence of characters separated by a delimiter. The delimiter represents a set of null-terminated characters containing the desired symbols that separate each of the tokens in the cString.

The return value is a pointer to the next token found in the first parameter. When no more tokens are found, NULL is returned. Be aware that each call to strtok modifies str by actually substituting a null character for each delimiter encountered. Example 12.8.1 demonstrates how this function can be used.

images

images

In Example 12.8.1, after the first call to strtok, the first parameter is replaced with NULL. This may seem confusing, but it allows strtok to work off of the original cString and be able to keep its position within the string.

12.8.2 The strstr Function

Another cString function we can now discuss is strstr. This function is designed to return a pointer to the first occurrence of a target cString within an existing cString.

Following is the syntax for the strstr function:

char * strstr ( const char * str, const char * searchString );

The str parameter is the null-terminated string we want to search. The parameter searchString represents the sequence of characters we are looking for.

The value returned by the strstr function is a pointer to the first occurrence of searchString in str, or NULL if searchString is not found. Example 12.8.2 illustrates how the strstr function is used to search for “World” in str and, if found, replace it with “Earth”.

images

In Example 12.8.2 we made it a point to check the value returned from the strstr function to make sure that the string “World” was indeed contained within our cString. Had the value not been found, the function would have returned NULL.

12.8.3 The strchr Function

The final cString function we are going to explore is strchr. This function locates the first occurrence of an individual character in a cString. If the specified character is not found, the function returns NULL. If the character is found, the function returns a pointer to the first occurrence of the character in our original cString.

Following is the syntax for the strchr function:

char * strchr ( const char * str, int ch );

The str parameter is the cString we want to perform the search on, while ch represents the individual character we are looking for.

The return value is a pointer to the first occurrence of the character, or NULL if the individual character is not found. Example 12.8.3 demonstrates how to find and print the location of all the “n”s in the title of our text, “C++: An Active Learning Approach.”

images

As we had done in our previous example, we checked the value returned from the strchr function call to make sure the character was contained within the cString.

12.8.4 The memset Function

The memset function is a very useful function that sets a block of memory to a specified value. Although this function is usually thought of as a string-handling function, it actually works with any block of memory. The syntax for memset is as follows:

void * memset ( void * dest, int c, size_t count );

The first parameter is a pointer to the block of memory. The void pointer means we are able to pass a pointer to any block of memory. The second parameter is the value to be stored in the block of memory, and the third parameter is the number of bytes to set. Example 12.8.4 demonstrates the memset function.

images

The code shown in Example 12.8.4 demonstrates that memset works well with any data type. When the code is done executing, the block of memory represented by “grades” will be completely full of zeros. Likewise, the letter_grades array will have the character “F” in all of its elements.

As stated before, the other functions in the same category as memset work in a similar manner. Now that you've seen how one function works, you should be able to use the other functions without any problem. Look at your help files to read more about these other functions.

Section 12.8 Learn by Doing Exercise

1.  Write a function that takes two parameters, a pointer to a cString and a single character, and returns the number of instances of the character found in the cString. Write a program that tests that your function is operational.

12.9 Dynamic Memory Allocation

A disadvantage with arrays is the need to estimate the maximum number of elements when the array is declared, and then live with that estimate throughout the execution of our program. If the estimate should prove to be inaccurate, we've either wasted space or created a situation where there isn't enough room for the data to be stored. Another disadvantage is the need to specify the size of the array during its declaration with a constant or literal rather than a variable. Dynamic memory allocation allows us to overcome these disadvantages.

The memory required for your variables is automatically allocated when your program is loaded into memory. This special area within memory is called the stack. Each program that is running has its own stack. The memory not taken up by the operating system or running programs is called the heap.

Dynamic memory allocation requests a specific amount of memory from the heap. The operating system tries to honor the request and marks the memory as being allocated to your program.

One advantage to using dynamic memory allocation is that we only use as much memory as we need. Dynamic memory allocation is required when the amount of memory needed is unknown until runtime.

One of the disadvantages of dynamic memory allocation is that it will cause a speed degradation in your program. Most of the time, however, you will not notice the difference in speed. Another disadvantage is that if the memory is not released it will cause memory leaks.

We could also create a situation called a dangling pointer, in which a pointer contains an address that is no longer valid. As an example, suppose you have two pointers pointing to the same piece of memory on the heap. If one of the pointers is deallocated, the other pointer now has an address that can no longer be legally accessed. If the second pointer is accessed, your program will crash.

The next two sections demonstrate the C++ techniques needed to allocate and deallocate memory.

12.9.1 The new Operator

The new operator makes a request for memory to the operating system. If the operating system can fulfill the request, the new operator will return a pointer to the newly allocated memory. If there isn't enough memory to satisfy the request, your program will either throw an error or new will return 0, depending on your compiler. The techniques involved in recovering from this error are beyond the scope of this text. The syntax of the new operator is as follows:

<target-type> * <ptr-name> = new <target-type>;

As you can see from the syntax, the target type of the pointer must match what is specified in the new statement. Example 12.9.1 shows how to use the new operator.

images

Example 12.9.1 allocates enough room for a single integer and stores the returned address in the pointer ptr1. The allocated memory is not initialized and therefore contains garbage. Example 12.9.2 shows how to use new to initialize the memory.

images

Both statements in Example 12.9.2 allocate enough memory for their respective data types and then initialize the new memory. In the case of the character pointer, we initialize the memory with the character ‘A’. In the second example, the empty parentheses specify that the memory is to be initialized with the default value for that data type. This is a shortcut way of initializing the memory to 0.

We can also use new to dynamically allocate arrays, as shown in Example 12.9.3.

images

The first new statement in Example 12.9.3 demonstrates that we can use a variable to specify the number of elements to allocate. The second example allocates an array of 15 character pointers. Notice that the address returned is an address of an address of a character. Also note that you can't initialize an array in the new statement.

Now that the process of allocating memory is known, it is necessary to determine how to deallocate memory so that memory leaks aren't introduced into your program. As we've said before, good programmers clean up after themselves. It is imperative that you free any dynamic memory that has been allocated to your program.

12.9.2 The delete Operator

Even though there are many different forms associated with the new operator, there are only two forms for the delete operator. The syntax for both forms is as follows:

delete <ptr-name>;

delete [] <ptr-name>;

The first form is used to deallocate a single piece of memory, while the second form deallocates a dynamically allocated array. Example 12.9.4 uses delete to deallocate memory.

images

images

Notice the difference between the two forms of the delete statement in Example 12.9.4. The second delete statement includes empty brackets, which designate that this statement deallocates an array. These brackets will always be empty. The first delete statement doesn't include brackets, indicating that it will deallocate only a single piece of memory.

Another thing to remember is that the address referenced by the pointer must be the same one returned by the new operator. In other words, when the array was allocated, the address of the memory was returned and stored in a pointer. If the pointer is later manipulated so that it is pointing to a different element within the array, that pointer can't be used in the delete statement without returning it to its original address.

There are a few situations in which the execution of the delete statement could cause your program to crash. The first two situations will occur if the address in the pointer is invalid or has already been deallocated by another delete operation. Since it is valid to delete a null pointer, it is good practice to initialize your pointers to null. Be aware, however, that after deallocation, the pointer will still contain the address that was just used to deallocate the memory. Should you attempt to reference or deallocate this pointer again, your program will crash because you no longer own that memory.

Another cause of abnormal program termination is when the deallocation process indicates that the area around the memory to be deallocated has been corrupted. This means that if we go out of bounds of a dynamic array, the program will crash when the delete operation is performed. As you might suspect, this error is extremely difficult to track down, making this another reason why it is important to make sure you do not go out of bounds of any array.

Section 12.9 Exercises

1.  Explain the purpose of the new operator.

2.  Explain each of the following lines of code:

a. char * ptrA = new char;

b. int * ptrB = new int[num_elements];

3.  Write the necessary statements to release or free the memory allocated in Exercise 2.

4.  True or false: When you use brackets to allocate memory for an array, you must use brackets when deleting that memory.

5.  What is wrong with each of the following code fragments?

images

Section 12.9 Learn by Doing Exercise

1.  Write a program that accepts from a professor the number of students in a class. Allocate enough room to store final exam scores for all of the students. Display to the screen all of the scores and the average of the scores.

12.10 Passing Pointers by Reference

In a previous section we learned about passing by pointer. But what if we want to pass the pointer itself, so that it can be changed within a function? We have two options; pass by reference or pass by pointer. Example 12.10.1 demonstrates how to pass a pointer by reference.

images

There is a lot going on in Example 12.10.1, so examine it carefully. The first thing to notice is the formal parameter in the function header where the character pointer is passed by reference.

Examining the rest of the GetString function we notice that a temporary local array is created to receive input from the keyboard, followed by the dynamic creation of an array that is exactly big enough for the string that was entered, plus one extra space for the null character. The string is then copied from buffer to the new string.

We didn't assign the address of the array to the pointer because local variables are destroyed when the function ends. If we had done so, when the flow of the program returned to main, the address assigned to the pointer would no longer be valid. Dynamically allocated memory, on the other hand, is only destroyed when it is deleted. Therefore, the address would still be valid even after the function ended.

The other way to pass a pointer to a function so that it can be changed is by passing by pointer, as demonstrated in Example 12.10.2.

images

images

We kept the code the same between Example 12.10.1 and Example 12.10.2 except that the first example passes by reference and the second passes by pointer. Passing by pointer complicates things a little by requiring a few extra asterisks, but even that little inconvenience isn't too bad. For those C programmers among us, you don't have a choice; passing by pointer is the only option.

Section 12.10 Learn by Doing Exercise

1.  One of the problems with strcat is that if there isn't enough room in the destination cString, there will be problems with your program. Write two versions of strcat that ensure that there will always be enough room in the destination cString by dynamically allocating the appropriate memory inside the strcat function. One version of the function will be passed the destination by reference, while the other will be passed the destination by pointer.

12.11 Pointers and Strings

In Chapter 10 we learned that an array can never appear on the left side of an assignment operator because the name of an array is a constant address. A pointer is a variable and therefore can appear on the left side of an assignment operator. Does that mean we can assign the contents of a cString to a pointer? No, but we can assign a string literal to a pointer—well, sort of. Look at the code in Example 12.11.1.

images

Example 12.11.1 appears to assign a string literal to the pointer. This is obviously not possible because a pointer only holds an address. What is actually happening is the string literal “Sherry” is stored in memory in a place called the symbol table. This table holds all of the literals (symbols) needed for your program. When a string literal is accessed, the address of that literal is returned. Therefore, the two pointers in Example 12.11.1 actually contain the same address.

These pointers can even be compared using relational operators. Be sure to remember, however, that all you are comparing are the addresses stored in the two pointers—not the actual strings. Another thing you shouldn't do is to delete a pointer that contains the address of a string literal. If attempted, your program will crash.

Please note that not all compilers optimize this code the same way. Visual Studio, for example, stores duplicates of the string literals. Therefore, the two pointers in Example 12.11.1 would contain different addresses.

12.12 Ragged Arrays

As stated before, pointers allow us to use only the amount of memory needed. We don't waste memory by declaring an array with a constant size and not using all of the elements. Look at the variables declared in Example 12.12.1.

images

All three of the variable declarations in Example 12.12.1 accomplish the same thing in three different ways. Figure 12.12.1 graphically illustrates these differences.

As you can see after examining the diagram, the third example uses the least amount of memory. The first example is the worst because of the wasted elements. The second example doesn't seem too bad until you realize the symbol table still contains the string literal and that now there is a copy as well.

We can extend this efficient use of memory philosophy one step further by using an array of pointers to save even more space. This is called a ragged array. Example 12.12.2 declares a ragged array.

images

Figure 12.12.1 Differences in string assignments

images

Figure 12.12.2 Ragged array

images

As you can see, an array of six character pointers is declared, the first three of which are initialized to string literals. Figure 12.12.2 is a visual representation of this ragged array.

Notice that the right edges of the literals are not even, hence a ragged array. Ragged arrays are especially useful in the creation of a constant array of string literals, as shown in Example 12.12.3.

images

As you can see from Example 12.12.3, the two ragged arrays can be used in a variety of programs that use a standard deck of cards. It makes sense to declare them this way because the values will never change and because it takes up the least amount of memory while still keeping all of the values together. We will revisit these ragged arrays in Chapter 13. The next section extends the concept of ragged arrays into the realm of dynamic arrays.

Section 12.12 Exercises

1.  What is a ragged array?

2.  For each of the following statements, draw a diagram that represents how the data is stored in memory.

images

12.13 Dynamic Two-Dimensional Arrays

In the last section we learned how to create a ragged array. This section takes the concept one step further by examining how to create a dynamic ragged array.

The simplest way to create a dynamic ragged array is by first knowing the number of strings to be stored. If this value is known, we can create a character pointer array of the correct size. Since we are requesting an address of a character pointer, it is necessary to declare a two-star character pointer. The first step in the creation of a dynamic ragged array—the declaration of the pointer and the allocation of the array—is shown in Example 12.13.1.

images

Now that we have an array of character pointers that was dynamically allocated in the last statement of Example 12.13.1, we can use the technique described earlier in this chapter to create an array of characters that is exactly the needed length. This second step in the process is demonstrated in Example 12.13.2.

images

In Example 12.13.2, we assigned the address of the dynamic array of characters to an element in the pointer array, which allows us to retain all of the names entered. This technique works fine, but it isn't always feasible to ask the user how many elements are needed. Another approach is presented in Example 12.13.3, which shows changes to the previous method.

images

All we did in Example 12.13.3 was add another two-star character pointer. The variable temp will contain the address of the array, into which we will copy all of the addresses of the previously entered names.

The next step is to allocate the memory needed for our temporary array and copy all of the addresses associated with any previously entered names into the new array. This is shown in Example 12.13.4.

images

Figure 12.13.1 illustrates how these variables might look in memory, assuming that two names have already been entered.

In Figure 12.13.1, temp now points to an array that is one bigger than the array names points to. Also, the first two pointers of each array are pointing to the same strings. The names weren't copied; the pointers were just individually assigned from one array to the other.

The next step requires us to read in the new name, allocate the exact amount of memory needed for the name, and store the address of the name at the end of the temporary array. We will then need to copy the name from the buffer into the newly allocated memory. This step is shown in Example 12.13.5.

images

Figure 12.13.1 Step 2 memory layout

images

Figure 12.13.2 Step 3 memory layout

images

Figure 12.13.2 illustrates the process presented in Example 12.13.5.

images

Figure 12.13.3 Step 4 memory layout

The last step is to clean up the old array pointed to by names, and make names point to the newly generated array pointed to by temp. This process is shown in Example 12.13.6.

images

The process involved in Example 12.13.6 is demonstrated in Figure 12.13.3. Notice that only the old array pointed to by names is deleted, not the strings themselves.

After this process has been completed, names is now pointing to the array that contains pointers to all of the strings. Everything is now set up for the entry of the next name.

The process described in this section can be fairly memory- and processor-intensive, as it requires a lot of memory allocation and deallocation, which has a tendency to decrease your program speed. However, if you understand this process, you have a good understanding of pointers. These dynamic arrays are extremely useful and make good use of available memory.

It is imperative that you don't forget to deallocate all of the memory used in your dynamic arrays. Not doing so will quickly diminish your computer's memory resources because of the numerous memory leaks introduced into your program. This process is shown in Example 12.13.7.

images

One common mistake that students make is deleting the array without deleting each name pointed to by the elements of the array. This mistake causes major memory leaks. Since memory leaks are a common situation, Visual Studio has included functions to test for them. This process is described in the next section.

Section 12.13 Learn by Doing Exercise

1.  Write a program that stores stock symbols into dynamic arrays. The user will enter as many stocks as he or she wishes. The program should display the list of stocks after each one is entered. For an extra challenge, sort the stocks in alphabetical order.

12.14 Testing for Memory Leaks

Visual Studio includes some functions that check for memory leaks during the debugging process. Although in C++ programs you can't tell where your memory leaks have been introduced into your program, you can at least check for their existence. Example 12.14.1 shows a very simple program with a memory leak as well as the code necessary to check for this situation.

images

images

images

Figure 12.14.1 Output window

The #define should be performed prior to any #includes. Also, the function call to _CrtSetDbgFlag should be the first line of code in main. To test for leaks, run your program using the Start Debug option. When the program exits, the Output window will display the existence of any memory leaks. Figure 12.14.1 shows the Output window after the execution of this program.

Notice that the window displays the message “Detected memory leaks!” The window also shows the beginning address of the memory that was allocated; how many bytes were not deallocated; and the data, displayed in hexadecimal format.

This is an extremely useful debugging tool that should be included during the debugging process for any program using dynamic memory allocation. You should also be aware that there are many third-party software tools that perform memory leak detection. Some of these programs are free of charge while others are very expensive.

Section 12.14 Learn by Doing Exercise

1.  Using the program you wrote in Section 12.13 Learn by Doing Exercise, add the code necessary to detect any memory leaks. Run your program, and if you are lucky enough not to have any leaks, comment out the delete statements and rerun the program to see the results of the memory leak testing.

12.15 Function Pointers

When we call a function in our program, the operating system knows what to do and where to get the instruction set for that function. We may not have thought about it before, but it makes sense that the function exists somewhere in memory. Therefore, we can get the address of the function and store that address in a pointer—to be more specific, a function pointer. Sounds like fun, doesn't it?

12.15.1 Declaration

When we declared pointers to variables, it made a little sense because we were already familiar with the target types for the pointers. It is difficult to visualize what the target type of a function pointer would look like. The declaration of a function pointer will look similar to the function signature. Therefore, any function whose address is stored in the pointer will have the same signature.

Example 12.15.1 shows various function pointer declarations.

Function pointer declarations

images

The parentheses around the asterisk and the pointer name—(*fn_ptr1)—are required. Without the parentheses, the compiler would interpret the line of code as a function declaration. For example, without the parentheses, the second declaration in Example 12.15.1 would be the declaration of a function that returns an integer pointer and is passed nothing.

As you may have guessed, we can not only dynamically allocate a function pointer but also dynamically allocate an array of function pointers, as shown in Example 12.15.2.

images

In Example 12.15.2, we do not allocate a function—only function pointers.

12.15.2 Retrieving a Function's Address

Now that we have a function pointer, we need to retrieve a function's address. Just like an array, whose name references the beginning address of the array, a function's name references its beginning address. Example 12.15.3 shows the process of retrieving a function's address.

images

Similar to any other pointer, the target types must match. Therefore, the function signature must match the target type of the pointer.

12.15.3 Indirectly Calling a Function

Now that an address of a function is stored in the pointer, we need to determine how to call the function using the function pointer. One method is to use the function pointer just like we use the name of a function. Since both the pointer and the name are addresses, the syntax is the same. This is one of the two methods to call a function using a function pointer. The other method uses an asterisk to signify that we are using a function pointer. Both methods are shown in Example 12.15.4.

images

STYLE NOTE Both of the methods shown in Example 12.15.4 accomplish the same task. However, some programmers prefer one method over the other. We actually prefer the second method because it is more visually indicative that a function pointer is being used.

12.15.4 Uses of Function Pointers

We now know how to create function pointers, but it is probably unclear why we would want to do so. The reasons aren't easy to see at this point. Later in the text we will discuss some of the more advanced concepts of object-oriented programming with C++, such as polymorphism, which make heavy use of function pointers.

A currently relevant example of the use of function pointers is seen in the predefined function qsort. This function sorts arrays of any type. How is this possible? To sort something, you need to be able to compare two of the elements to be sorted, yet cStrings and integers are compared differently. So how can the same function sort different types of data? The answer lies in the following function signature of qsort:

images

This example might seem confusing, but we'll try to clarify things a little. First of all, what is a void pointer? A void pointer is a pointer to anything. The memory pointed to by the void pointer is like a lump of clay waiting to be molded into whatever we need. What about size_t? As previously discussed, when presenting strlen, size_t is nothing more than an unsigned int. The last parameter is a function pointer that holds the address of a user-defined function, which determines how to compare two of the elements in the array. Example 12.15.5 demonstrates how to use the qsort function.

images

images

Although there is a lot going on in Example 12.15.5, the thing we want to point out is that the function call to qsort accepts as its last parameter the address of the two compare functions. The qsort function requires the compare function to have a specific signature. The compare function must return less than 0 if arg1 is less than arg2, 0 if the two arguments are equal, and greater than 0 if arg1 is greater than arg2. If you run the code in Example 12.15.5, the output consists of the two arrays displayed in ascending order.

Another example that uses function pointers is a predefined function called bsearch. This function behaves similarly to qsort, except that bsearch performs a binary search on the array elements. This is the same binary search algorithm we developed in an earlier chapter. Although they might not be an important tool in your toolbox for quite some time, function pointers are a necessary component of C and C++ programming.

Section 12.15 Learn by Doing Exercise

1.  Modify the program in Example 12.15.5 so that it sorts in descending order.

12.16 Problem Solving Applied

As stated previously, stepwise refinement is an extremely effective programming technique. Another helpful technique for designing programs that have complicated algorithms or extensive use of pointers is to draw pictures to help you understand and visualize the process. Both of these techniques will be demonstrated as we develop a function to delete a specific stock from the program written for Section 12.13 Learn by Doing Exercise.

The first part of the stepwise refinement process is to develop a very general algorithm for the task we are trying to solve. Don't go into very much detail at this point because the algorithm will be expanded as we proceed. Figure 12.16.1 shows this very high level look at our algorithm.

Once the general algorithm has been developed, expand each step as necessary into appropriate pseudocode. Some pieces of the algorithm may not warrant drawing the diagram before developing the pseudocode. The first step of the delete algorithm is a standard sequential search, and is shown in Figure 12.16.2.

images

Figure 12.16.1 High-level algorithm to delete a stock

images

Figure 12.16.2 Step 1 Pseudocode: Find index of stock to be deleted

images

Figure 12.16.3 Step 2 Diagram: Allocate new array of stock pointers

As shown in Figure 12.16.2, when this step is finished, the index of the target stock is known.

Since the next step is more complicated and deals directly with pointers, it might be a good idea to draw the diagram of what you are trying to accomplish in relation to Step 2 of the general algorithm. This is shown in Figure 12.16.3.

The pseudocode for the second step shown in Figure 12.16.4 may be fairly trivial, but creating the diagram in Figure 12.16.3 allows us to develop this algorithm in a step-wise manner.

The third step requires us to copy the individual pointers from the old array into the new array. The copying process will stop when the target stock's index is reached. The diagram for the third step is shown in Figure 12.16.5.

The pseudocode for the third step is shown in Figure 12.16.6.

The fourth step requires us to delete the target stock, as shown in the diagram in Figure 12.16.7.

Again, the pseudocode is not very exciting, but it is an extremely important step; it is shown in Figure 12.16.8.

images

Figure 12.16.4 Step 2 Pseudocode: Allocate new array of stock pointers

images

Figure 12.16.5 Step 3 Diagram: Make new array point to any stocks located before the one to delete

images

Figure 12.16.6 Step 3 Pseudocode: Make new array point to any stocks located before the one to delete

In Step 5, the pointers to the stocks that are located after the deleted stock must be copied into the new array. This process is shown in Figure 12.16.9.

The pseudocode for Step 5 is shown in Figure 12.16.10. Notice the similarities between Step 5 and Step 3.

Now that everything has been copied over into the new array, we need to deallocate the old pointer array and assign the stock pointer to the new array. This process is shown in Figure 12.16.11.

The pseudocode for Step 6 is shown in Figure 12.16.12.

Since the rest of the algorithm is fairly straightforward, no diagrams are necessary. To make it easier to view the entire algorithm, all steps are shown in Figure 12.16.13.

The process in this section may seem like a lot of work. However, it is our experience that programs developed using stepwise refinement and related diagrams are usually developed in less time and have fewer bugs.

images

Figure 12.16.7 Step 4 Diagram: Deallocate desired stock

images

Figure 12.16.8 Step 4 Pseudocode: Deallocate desired stock

images

Figure 12.16.9 Step 5 Diagram: Make new array point to any stocks located after the one deleted

images

Figure 12.16.10 Step 5 Pseudocode: Make new array point to any stocks located after the one deleted

images

Figure 12.16.11 Step 6 Diagram: Deallocate stock array and assign stock pointer to new array

images

Figure 12.16.12 Step 6 Pseudocode: Deallocate stock array and assign stock pointer to new array

images

Figure 12.16.13 Delete algorithm pseudocode

12.17 C—The Differences

There are a few differences between C and C++ in terms of the processes in this chapter. One of the basic differences is that C uses a predefined constant, NULL, to initialize pointers. Although using 0 would still work, its use is discouraged in some C programming circles. Since NULL is predefined but not a reserved word, its definition is located in a header file. Many of the C header files, including <stdio.h>, have a definition for NULL.

12.17.1 Dynamic Memory Allocation

In C++ we learned about the new operator for dynamic memory allocation. This operator is not available in C. C programmers must use the malloc function to dynamically allocate memory. Actually, C++ programmers do this as well—they just don't realize it. The new operator is nothing more than a wrapper or shell around the malloc function. Example 12.17.1 illustrates C++ dynamic memory allocations and the corresponding C statements.

images

If you examine the C statements in Example 12.17.1, you'll notice that the only parameter to malloc is the number of bytes to be allocated. This demonstrates why, if allocating an array, we need to specify the size of each element and then multiply it by the number of desired elements. The other thing to notice is that the pointer returned from malloc must be typecast to whatever target type we need. This is because malloc returns a void pointer, which must be molded or converted for our specific use by typecasting. To use malloc, you must include <malloc.h> or <stdlib.h>.

Initialization of the memory is not as robust as in C++. A C function called calloc allows you to initialize the memory, but only to all zeros, you cannot specify the value with which to initialize the memory. Another powerful function available to only C programmers is called realloc. This function allows you to change the size of an allocated block of memory.

12.17.2 Dynamic Memory Deallocation

Fortunately, deallocating memory is simpler in C than allocating memory. The free function deallocates any memory allocated with malloc. One big difference between free and delete is that some older specifications for free state that the program will crash if the pointer is null when an attempt to deallocate the memory is made. Newer versions of free don't have this limitation, but this is something important to be aware of. Example 12.17.2 demonstrates the free function.

images

12.17.3 Memory Leak Detection

Usually C programmers don't have all of the nice tools and programming constructs that C++ programmers do. So why should memory leak detection be any different? Surprise—it is different, and it's much better.

We stated in Section 12.14 that the leak detection function couldn't determine where in your code the memory was originally allocated. This is because malloc is what actually requests the memory from the operating system. Therefore, because the new operator is a wrapper around malloc, all memory leaks in C++ are started from the call to malloc. When using C++, double-clicking on the memory-leak error message in the Output window will always transfer you to the code for the new operator. However, in C you will be transferred to the actual malloc call within your code whose memory didn't get freed. You don't need to do anything different to use this feature of the leak-detection routine.

12.18 SUMMARY

As you almost certainly determined, this chapter has to rate as the most demanding, challenging, mentally stimulating, and exciting chapters so far. It introduced a number of very powerful concepts, all centered around pointers and memory. We began by discussing the fundamental idea of holding an address in a pointer. From there, we covered the concept of what pointers are, examined how they are declared, and illustrated some of their possible uses. During the discussion of using pointers, the indirection operator was introduced as a means to retrieve or access the value that a pointer is pointing to.

Once we presented the fundamental theory associated with pointers, we introduced the idea of passing variables by pointer—which was hopefully made easy by our past presentation of passing by reference. As a result, once the syntactical details were provided and discussed, you had another powerful option for passing data into functions in order to manipulate or alter it.

Another of the fundamental aspects associated with pointers covered was the topic of pointer arithmetic. Having the ability to perform arithmetic on pointers offered some additional methods for accessing or manipulating data, including providing an alternative to use in efficiently iterating through an array.

A few chapters ago we introduced a number of predefined functions available for manipulating cStrings. After presenting the fundamental aspects associated with pointers, this chapter discussed some additional cString functions, including strtok, strstr, and strchr. In addition, the ability to set an entire block of memory to a specific value via the function memset was also illustrated.

Another exciting concept dealt with dynamic memory allocation, in which the programmer makes a request via the new operator for a specific amount of memory from the operating system at runtime. Along with this capability came some additional programmer responsibility—for example, the need to make it a point to always deallocate, or delete any memory. To help the programmer make sure that all memory allocated has been returned to the operating system, we provided a short discussion illustrating some of the functions provided by Visual Studio for checking for memory leaks during the debugging process.

Finally, two additional concepts were presented: ragged arrays and function pointers. The idea of using an array of pointers, or ragged arrays, was presented as a more memory-efficient alternative to two-dimensional arrays. Function pointers were discussed in terms of their ability to store the address of a function and indirectly call the function. In conjunction with function pointers, we presented the predefined function qsort.

While you no doubt had to expend some additional energy studying the material presented in this chapter, we believe you will find this knowledge extremely valuable in a wide range of programming settings. Knowing some of the characteristics associated with pointers will likely serve you well as you continue to expand your programming knowledge and gain additional experience.

12.19 Debugging Exercise

Download the following file from this book's website and run the program following the instructions noted in the code.

images

images

images

12.20 Programming Exercises

The following programming exercises are to be developed using all phases of the development method. Be sure to make use of good programming practices and style, such as constants, whitespace, indentation, naming conventions, and commenting. Make sure all input prompts are clear and descriptive and that your output is well formatted and looks professional. Also, if available, make sure to use memory leak detection in all programs.

1.  Write a program that includes a user-defined function that emulates the predefined strcmp function. Within the body of your compare function, use only pointer notation to manipulate the cStrings. Make sure to introduce into your program the code necessary to appropriately test your function. Exercise all possible control paths within your function, and clearly print out both strings and the results of each of your comparisons.

2.  Write a program that reads in a text file one word at a time. Store a word into a dynamically created array when it is first encountered. Create a parallel integer array to hold a count of the number of times that each particular word appears in the text file. If the word appears in the text file multiple times, do not add it into your dynamic array, but make sure to increment the corresponding word frequency counter in the parallel integer array. Remove any trailing punctuation from all words before doing any comparisons.

Create and use the following text file containing a quote from Bill Cosby to test your program.

I don't know the key to success, but the key to failure is trying to please everybody.

At the end of your program, generate a report that prints the contents of your two arrays in a format similar to the following:

Word Frequency Analysis

WordM

Frequency

I

1

don't

1

know

1

the

2

key

2

 

3.  Write a program that implements all of the sections (Section 12.13 Learn by Doing and 12.16 Problem Solving Applied) previously discussed relating to a stock tracking program. Your menu-based program should include the following options:

a. Add a new stock.

Include room for the stock symbol, the name of the stock, and the current price.

b. Edit a stock.

Provide the ability to locate a stock based on its respective symbol and change the current price.

c. Delete a stock.

Offer the user the opportunity to enter a stock symbol and, if found, to delete the stock. If the particular stock symbol is not found, simply display an informative message to the user.

d. Display all stocks.

Display a list of all the current stocks stored in your array.

4.  Write a program that allows Marcia, an avid cook, to keep an inventory of her spices and herbs. These ingredients will be stored in a text file called pantry.txt. When your program is executed, the ingredients from the data file will be stored in a dynamic array. A menu with the following options will then be displayed.

Menu Option Description
Add Ingredient Prompt for and add an ingredient to the dynamic array.
Remove Ingredient Remove a specific ingredient from the dynamic array.
Search for Ingredient Search for the existence of a specific ingredient.
Edit Ingredient Search for an ingredient and allow the user to change the spelling of a specific ingredient.
Save Ingredients to File Write the dynamic array to pantry.txt, saving any changes.
Display Ingredients Display all of the ingredients to the screen.
Check Recipe for Ingredients Prompt for the filename of a recipe text file. Read the contents of the recipe file a line at a time and store it into a dynamic array. Then search for the existence of the spices and herbs that are currently stored in the dynamic arrays in the recipe. The ingredients in the recipe file are guaranteed to be surrounded by < >. Display any ingredients that are in the recipe file that aren't found in the pantry.
Exit Exit the program and save the changes to the pantry file.

Table 12.20.1 Menu options

A sample pantry.txt file is shown here.

Basil
Flat Leaf Parsley
Thyme
Sage
Cumin
Steak Seasoning
Mace
Garlic Powder

Table 12.20.2 Pantry file

Search the Internet for appropriate recipes. Modify the recipes to surround spices and herbs with < >. For example, a recipe using basil will need to be modified so it appears as <Basil>.

12.21 Team Programming Exercise

Bruce, a senior software engineer, has given you a function to write. He is insistent that your function have a very specific signature, which he has provided. The function is to take a cString and convert it to an array of cStrings based on a delimiter that is passed to the function. The function is to return the number of cStrings in the dynamic array. The function signature, as well as some preliminary documentation Bruce has given you, is found below. Bruce has informed you that under no circumstances should you change the function signature.

images

images

Bruce, an ex-Marine, is known to be a person who is pretty tough to please. He will want a program that exercises the function to prove that it works under a wide variety of circumstances. You also know that memory leaks will not be tolerated.

12.22 Answers to Chapter Exercises

Section 12.1

1. d. all of the above.

2. false

Section 12.2

1. target type, data type

2. This is a pointer declaration of a variable called ptr with the target type of double. The pointer is initialized to null.

3. int * ptr1 = 0, * ptr2 = 0;

4. a. invalid—missing address of operator

int num = 0;

int * ptr = &num;

b. invalid—missing asterisk

int num = 0;

int * ptr = &num;

c. invalid—target type and data type don't match

char ch = ‘A’;

char * ptr = &ch;

d. invalid—doesn't need the address-of operator because ray already refers to an address

char ray[15] =“Ray”;

char * ptr = ray;

5. a. declaring a pointer and initializing it to the beginning address of the array

b. declaring a pointer and initializing it to the beginning address of the array

Section 12.4

1.  A two-star pointer is a pointer that has two levels of indirection. This means that the pointer will hold the address of an address.

2.  Indirection is a method of referencing a variable in a roundabout way, rather than directly accessing the variable by its name. Accessing a variable's value via its memory address rather than its name is an example of indirection.

3.  The indirection operator is the asterisk (*). It removes one level of indirection.

4.  A cString is declared and initialized to the string literal “Randy”. A character pointer is declared and initialized to the address of the cString. The pointer is then dereferenced and the character ‘M’ is assigned to where the pointer is pointing, thus changing the cString to “Mandy”.

5.

images

Section 12.5

1.  In passing by reference, an alias, or reference, is created. This is nothing more than a wrapper around a pointer. However, the reference allows us to use the variable like any other variable without any additional syntax. Passing by pointer requires the programmer to handle all of the extra syntax required of pointers.

2.  False, because the parameter may already hold an address. If the parameter is already an address, placing an ampersand in front of the parameter will pass the address of an address.

Section 12.6

1.  a. Alex

b. is

c. 111.1 23.23 45.67 12.21 99.99

Section 12.7

1.  The purpose of this function is to test your knowledge of pointers. This function inserts a string into the middle of another string. What follows is a brief explanation for each of the respective lines of code.

images

Section 12.9

1.  The operator new makes a request to the operating system for memory. If there is memory available, new returns a pointer to the newly allocated memory.

2.  a. A request is being made to the OS for enough memory to hold one character. The address returned will be assigned to the character pointer called ptrA.

b.  Enough memory to hold an array of integers is being dynamically requested from the OS. The address returned will be assigned into an integer pointer called ptrB.

3.  a. delete ptrA;

b. delete [] ptrB;

4.  true

5  a. Need to declare ptr as a pointer.

Corrected: int * ptr = new int;

b.  The target type of the pointer must match the type specified in the new statement.

Corrected: char * ptr = new char;

c.  Must identify how many individual elements are to be dynamically allocated.

Corrected: char * ptr = new char[num];

d.  If brackets are used in allocating memory, use brackets when deleting that memory.

Corrected: delete [] ptr;

e.  The value returned from the new operator is an address of an address of a character.

Corrected: char ** ptr = new char * [15];

f.  In the second line of code, the original address contained in ptr has been incremented and no longer points to the original address returned from the new operator in the previous line.

Corrected: delete [] (ptr - 1);

g.  By assigning the contents of ptr2 to ptr in the last line of the code, there is no way of referencing the original address returned from the first new statement.

To correct: store the original address in another variable so it can be deleted.

h.  In the second line of code, both ptr2 and ptr now refer to the same address. In the third line that address is deleted. The final line attempts to assign the character ‘A’ to an area of memory that has technically been deleted.

To correct: Once you delete memory, do not reference it later.

Section 12.12

1.  A ragged array attempts to make efficient use of memory by using an array of pointers to point to individual elements. Each of the individual arrays contain only the minimum number of elements needed to hold the respective data.

a. char teams[5][10] = {“Seahawks”, “Packers”, “Bears”};

images

b. char teams1[][10] = {“Seahawks”, “Packers”, “Bears”};

images

c. char * teams2[5] = {“Seahawks”, “Packers”, “Bears”};

images

d. char * teams3[] = {“Seahawks”, “Packers”, “Bears”};

images

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

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