Previous | Table of Contents | Next |
Pointers are convenient for writing string-handling functions. For example, you could write your own version of the strcpy function. In practice, this is not necessary, because the standard library provides this function. However, it is useful to see how easy such a function is to write in C++.
char *strcpy (char *dest, char *src) { while (*src != \0) { *dest = *src; dest++; src++; } *dest = \0; // Add terminating null return dest; }
There are at least a couple of ways to make this code more compact, although this version is probably the easiest to understand. (However, as an exercise, you can apply some of the techniques in the previous section to reduce the size by at least a couple of lines.) In any case, Figure 3.9 illustrates how the function works.
Figure 3.9 How the strcpy function works.
Dynamic is one of those terms that get tossed around (even abused) a lot in the programming world. In general, dynamic means in motion or changing.
Dynamic memory is used when your memory requirements may change during running of the program. If youre reading the contents of a file into memory, for example, the amount of memory you need depends on the size of the file. If youre building a linked list of structures, you need to allocate new memory every time you add a new structure.
C++ provides two approaches to dynamic memory. You can use the malloc family of functions inherited from the C standard library, or you can use the new and delete operators, which are supported in C++ only.
In both cases, you have to understand and use pointers. With ordinary data structures, the compiler assigns an address to the variable or object. You never need to know this address; instead, you just use the variables name.
Calling the malloc function is a way of asking the operating system to give you a chunk of memory of a specific size. The operating system will oblige you if it possibly can (but note that in serious programs, you should test for error conditions). The result is that you get a pointer to this chunk, which consists of consecutive bytes in memory.
Using the malloc and free library functions involves the following steps:
#include <malloc.h>
pointer = (type *) malloc (elements * sizeof (type));
free (pointer);
In terms of programming techniques, the use of dynamic memory differs from using ordinary variables in one important way: when using dynamic memory, you have no direct access to the data. You have access only through a pointer. Fortunately, it is easy to refer to any part of a memory block through a pointer. In C++ (and in C), pointers and array names are almost interchangeable. Once the operating system allocates the chunk of memory, the operating system returns a pointer giving the starting address of this chunk.
Dont lose this pointer (or change its value without first saving it), because otherwise all ties to the memory are lost.
For example, the following code allocates a memory block of 500 integers (int). After malloc returns successfully, you can refer to any of the integers as members of an array. Use array indexing on the pointer itself.
#include <malloc.h> #include <stdio.h> ... int *p; p = (int *) malloc (500 * sizeof (int)); if (p) { p[0] 5; // Assign 5 to first element p[50] = -33; // Assign -33 to 51st element printf (The 51st element is %d.\n, p[50]); ... free (p); }
The malloc statement takes one argument: the number of bytes requested. Because room for 500 integers is wanted, the appropriate size is 500 times the size of each integer. The sizeof operator (which admittedly looks like a function) is a special built-in operator in C and C++ that returns the size of a type.
p = (int *) malloc (500 * sizeof (int));
This statement also uses a data cast, (int *), to change the type of the value returned. The malloc function returns a pointer of type void*, which is a generic pointer type. In C++, you must change this data type before assigning it. The data cast changes the type from void* to int* so that it matches the type of the pointer p.
(int *) expression
As another example, the following code allocates a block of 100 double-precision floating-point numbers (double):
double *p; p = (double *) malloc (100 * sizeof (double));
![]() | Requiring the data cast is one area where C++ adds restrictions to C. In C, you can be sloppy and freely assign between pointers of different types. In C++, you must explicitly change the data type of a void* pointer before assigning it to another type of pointer. The C++ approach is safer, because it prevents you from accidentally assigning between different kinds of pointers. Using an int* pointer to access an array of double, for example, is guaranteed to cause errors unless you are doing it deliberately and for good reason. |
After the malloc function returns, it is a good idea to test the result. If the function was successful, the pointer contains a valid address. Otherwise, the pointer is assigned the null value, which is numerically equal to zero. An if statement interprets a null value as false. The pointer value is zero (false) if the malloc function failed because of insufficient memory. A true value therefore indicates success.
if (p) { p[0] = 5; // Assign 5 to first element p[50] = -33; // Assign -33 to 51st element printf (The 51st element is %d.\n, p[50]); ... free (p) ; }
The final thing the code does is to explicitly free the memory block by calling the free function. This is the other key difference between dynamic memory and ordinary variables. If you dont get into the habit of freeing dynamic memory blocks, then your code is likely to eat up memory without returning it, resulting in memory leaks to the system. You dont want to do that!
The new and delete operators can be effectively used wherever you use the malloc and free functions. If you understand how to call malloc, using new is easy. The use of new and delete also offers other advantages:
In summary, then, you need to use new and delete if you are going to work with classes and object orientation in C++; otherwise, you can use malloc.
To allocate memory using new, place the type of the object after new. This type can be a primitive type, a class, or an array.
pointer = new type;
So, for example, you could allocate a single integer or an array of 500 integers:
int *p1, *p2; p1 = new int; // p1 points to a single integer p2 = new int[500]; // p2 points to first of 500 // integer
In C++, you can combine initialization with operators and function calls so that you can use new in the same statement that declares the pointer:
int *p2 = new int[500];
To free memory allocated with new, use the delete operator followed by a pointer name. If the memory involved the creation of an array (as in the immediately preceding example), then place empty brackets ([]) before the pointer name.
Therefore, use one of the following two statements, as appropriate.
delete pointer; (if one object was created with new) delete [] pointer; (if array was created with new)
The example in the previous section could have been written this way using new and delete:
#include <stdio.h> ... int *p new int[500]; if (p) { p[0] = 5; // Assign 5 to first element p[50] = -33; // Assign -33 to 51st element printf (The 51st element is %d.\n, p[50]); ... delete p; }
Note that the new operator returns a null value if there is insufficient memory, just as malloc does. For best results, you should test this value before using or deleting the new memory.
Previous | Table of Contents | Next |