Tools Menu

Chapter 10 - Using Pointers

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

Dynamic Memory
When a C/C++ program is compiled, the computer’s memory is broken down into four zones that contain the program’s code, all global data, the stack, and the heap. The heap is an area of free memory (sometimes referred to as the “free store”) that is manipulated by using the dynamic allocation functions malloc( ) and free( ).
When malloc( ) is invoked, it allocates a contiguous block of storage for the object specified and then returns a pointer to the start of the block. The function free( ) returns previously allocated memory to the heap, permitting that portion of memory to be reallocated.
The argument passed to malloc( ) is an integer that represents the number of bytes of storage that is needed. If the storage is available, malloc( ) will return a void *, which can be cast into whatever type pointer is desired. The concept of void pointers was introduced in the ANSI C standard and means a pointer of unknown type, or a generic pointer. A void pointer cannot itself be used to reference anything (since it doesn’t point to any specific type of data), but it can contain a pointer of any other type. Therefore, any pointer can be converted into a void pointer and back without any loss of information.
The following code segment allocates enough storage for 300 float values:
float *pf;
int inum_floats = 300;

pf = (float
*) malloc(inum_floats * sizeof(float));
The malloc( ) function has been instructed to obtain enough storage for 300 *, the current size of a float. The cast operator (float *) is used to return a float pointer type. Each block of storage requested is entirely separate and distinct from all other blocks of storage. Absolutely no assumption can be made about where the blocks are located. Blocks are typically “tagged” with some sort of information that allows the operating system to manage the location and size of the block. When the block is no longer needed, it can be returned to the operating system by using the following statement:
free((void *) pf);
Just as in C, C++ allocates available memory in two ways. When variables are declared, they are created on the stack by pushing the stack pointer down. When these variables go out of scope (for instance, when a local variable is no longer needed), the space for that variable is freed automatically by moving the stack pointer up. The size of stack-allocated memory must always be known at compilation.
Your application may also have to use variables with an unknown size at compilation. Under these circumstances, you must allocate the memory yourself on the free store. The free store can be thought of as occupying the bottom of the program’s memory space and growing upward, while the stack occupies the top and grows downward.
Your C and C++ programs can allocate and release free store memory at any point. It is important to realize that free-store-allocated memory variables are not subject to scoping rules, as other variables are. These variables never go out of scope, so once you allocate memory on the heap, you are responsible for freeing it. If you continue to allocate free store space without freeing it, your program could eventually crash.
Most C compilers use the library functions malloc( ) and free( ), just discussed, to provide dynamic memory allocation, but in C++ these capabilities were considered so important they were made a part of the core language. C++ uses new and delete to allocate and free free store memory. The argument to new is an expression that returns the number of bytes to be allocated; the value returned is a pointer to the beginning of this memory block. The argument to delete is the starting address of the memory block to be freed. The following two programs illustrate the similarities and differences between a C and C++ application using dynamic memory allocation. Here is the C example:
/*
*   malloc.c
*   A simple C program using malloc( ), free( )
*   Copyright (c) Chris H. Pappas and William H. Murray, 1998
*/

#include <stdio.h>
#include <stdlib.h>

#define ISIZE 512

void main( )
{
 int
* pimemory_buffer;
 pimemory_buffer=malloc(ISIZE
* sizeof(int));
 if(pimemory_buffer == NULL)
   printf(“Insufficient memory\n”);
 else
   printf(“Memory allocated\n”);
 free(pimemory_buffer);
}
The first point of interest in the program begins with the second #include statement that brings in the STDLIB.H header file, containing the definitions for both functions, malloc( ) and free( ). After the program defines the int * pointer variable pimemory_buffer, the malloc( ) function is invoked to return the address to a memory block that is ISIZE * sizeof(int) big. A robust algorithm will always check for the success or failure of the memory allocation, and it explains the purpose behind the if-else statement. The function malloc( ) returns a null whenever not enough memory is available to allocate the block. This simple program ends by returning the allocated memory to the free store by using the function free( ) and passing it the beginning address of the allocated block.
The C++ program does not look significantly different:
//
//  newdel.cpp
//  A simple C++ program using new and delete
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
// #include <stdlib.h> not needed for malloc( ), free( )

#define NULL 0
#define ISIZE 512

void main( )
{
 int
*pimemory_buffer;

 pimemory_buffer=new int[ISIZE];
 if(pimemory_buffer == NULL)
   cout << “Insufficient memory\n”;
 else
   cout << “Memory allocated\n”;
 delete(pimemory_buffer);
}
The only major difference between the two programs is the syntax used with the function free( ) and the operator new. Whereas the function malloc( ) requires the sizeof operator to ensure proper memory allocation, the operator new has been written to automatically perform the sizeof( ) function on the declared data type it is passed. Both programs will allocate 512 2-byte blocks of consecutive memory (on systems that allocate 2 bytes per integer).
Using void Pointers
Now that you have a detailed understanding of the nature of pointer variables, you can begin to appreciate the need for the pointer type void. To review, the concept of a pointer is that it is a variable that contains the address of another variable. If you always knew how big a pointer was, you wouldn’t have to determine the pointer type at compile time. You would therefore also be able to pass an address of any type to a function. The function could then cast the address to a pointer of the proper type (based on some other piece of information) and perform operations on the result. This process would enable you to create functions that operate on a number of different data types.
That is precisely the reason C++ introduced the void pointer type. When void is applied to a pointer, its meaning is different from its use to describe function argument lists and return values (which mean “nothing”). A void pointer means a pointer to any type of data. The following C++ program demonstrates this use of void pointers:
//
//  voidpt.cpp
//  A C++ program using void pointers
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
#define ISTRING_MAX 50

void voutput(void
*pobject, char cflag);
void main( )
{
 int
*pi;
 char
*psz;
 float
*pf;
 char cresponse,cnewline;

 cout << “Please enter the dynamic data type\n”;
 cout << “    you would like to create.\n\n”;
 cout << “Use (s)tring, (i)nt, or (f)loat ”;
 cin >> cresponse;
   cin.get(cnewline);
     switch(cresponse) {
       case ‘s’:
         psz=new char[ISTRING_MAX];
         cout << “\nPlease enter a string: ”;
         cin.get(psz,ISTRING_MAX);
         voutput(psz,cresponse);
         break;
       case ‘i’:
         pi=new int;
         cout << “\nPlease enter an integer: ”;
         cin >>
*pi;
         voutput(pi,cresponse);
         break;
       case ‘f’:
         pf=new float;
         cout << “\nPlease enter a float: ”;
         cin >>
*pf; voutput(pf,cresponse);
         break;
       default:
         cout << “\n\n  Object type not implemented!”;
    }
}
void voutput(void
*pobject, char cflag)
{
 switch(cflag) {
   case ‘s’:
     cout << “\nThe string read in:  ” << (char
*) pobject;
     delete pobject;
     break;
   case ‘i’:
     cout << “\nThe integer read in: ”
          <<
*((int *) pobject);
     delete pobject;
     break;
   case ‘f’:
     cout << “\nThe float value read in: ”  
          <<
*((float *) pobject);
     delete pobject;
     break;
    }
}
The first statement of interest in the program is the voutput( ) function prototype. Notice that the function’s first formal parameter, pobject, is of type void *, or a generic pointer. Moving down to the data declarations, you will find three pointer variable types: int *, char *, and float *. These will eventually be assigned valid pointer addresses to their respective memory cell types.
The action in the program begins with a prompt asking the user to enter the data type he or she would like to dynamically create. You may be wondering why the two separate input statements are used to handle the user’s response. The first cin statement reads in the single-character response but leaves the \n linefeed hanging around. The second input statement, cin.get(cnewline), remedies this situation.
The switch statement takes the user’s response and invokes the appropriate prompt and pointer initialization. The pointer initialization takes one of three forms:
psz=new char;
pi=new int;
pf=new float;
The following statement is used to input the character string, and in this example it limits the length of the string to ISTRING_MAX (50) characters.
cin.get(psz,ISTRING_MAX);
Since the cin.get( ) input statement expects a string pointer as its first parameter, there is no need to dereference the variable when the voutput( ) function is invoked:
voutput(psz,cresponse);
Things get a little quieter if the user wants to input an integer or a float. The last two case options are the same except for the prompt and the reference variable’s type.
Notice how the three invocations of the function voutput( ) have different pointer types:
voutput(psz,cresponse);
voutput(pi,cresponse);
voutput(pf,cresponse);
Function voutput( ) accepts these parameters only because the matching formal parameter’s type is void *. Remember, in order to use these pointers, you must first cast them to their appropriate pointer type. When using a string pointer with cout, you must first cast the pointer to type char *.
Just as creating integer and float dynamic variables was similar, printing their values is also similar. The only difference between the last two case statements is the string and the cast operator used.
While it is true that all dynamic variables pass into bit oblivion whenever a program terminates, each of the case options takes care of explicitly deleting the pointer variable. When and where your program creates and deletes dynamic storage is application dependent.

Books24x7.com, Inc 2000 –  


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

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