Resizing a Block of Memory

Sometimes you have to resize a block of dynamic memory after you have already stored data in it. The most common reason for this is the need to grow the block beyond the initial size given to the malloc() call. This often happens if the amount of data to be stored and processed is not known in advance (for example, when you are receiving data over a network connection).

The C library provides the realloc() function for this purpose. It takes two parameters: the address of the memory block to be resized and the desired new size of that block.

void *x, *y;
x = malloc(1000); // Initial block
y = realloc(x, 2000); // Doubled

Retaining the Original Address

Since realloc() can return a NULL pointer if the operation fails, you have to retain a copy of the old pointer. You should not overwrite the only copy of an address with realloc()'s return value by assigning it to the same pointer variable. In short, this could be bad:

x = malloc(1000);
x = realloc(x, 2000);

If realloc() fails and returns a NULL pointer, the initial address of the memory block returned by the malloc(1000) call is lost. Since that value can no longer be passed to free(), the originally reserved memory cannot be reclaimed until the program terminates. This is a memory leak (see the end of the chapter for more on memory leaks).

The safe usage pattern is to store and examine the return value of realloc() in a second pointer variable before committing it to the original one:

void *x, *y;
x = malloc(1000);
y = realloc(x, 2000);

This way, the initial value is still available in x if realloc() fails. If you want, you can use a simple conditional on y to reassign the value of x (to the new address) if realloc() worked:

if (y) x = y;


realloc() attempts to grow the original block to accommodate the new requested size. If successful, it returns the block's address unchanged. If it is not possible to grow the old block (because there is not enough adjacent free space in memory), realloc() will instead allocate a new block in a different part of the memory pool; copy over all of the old data to the new, bigger block; and return the address of that new block.

This means that upon success, realloc() returns the address of the resized memory block. This address might be identical to the one you passed in as parameter, but it doesn't have to be. Upon failure, realloc() returns the NULL pointer instead.

This example program asks the user for an arbitrary number of integers. The program stops asking as soon as the number zero or a letter is entered. The application will begin by assuming that a certain amount of memory is required to store these values. Once that amount of memory is used, more memory is requested using realloc(). Finally, all the numbers will be sorted (using the C library's qsort() function) and printed.

To grow a dynamically allocated array

1.
Create a new file or project in your text editor or IDE.

2.
Type the standard beginning lines of code (Script 10.4):

/* resize.c - Script 10.4 */
#include <stdio.h>
#include <stdlib.h>

Script 10.4. This more complex example sorts a list of submitted integers. The realloc() function can resize a block of memory, where the submitted integers are stored.


3.
Add a function prototype for the sort() function:

int sort(const void *x, const void
 *y);

This function assists the qsort() function.

4.
Begin the main function:

int main (void) {

5.
Define an integer pointer variable:

int *numbers;

This pointer variable will hold the base address of the dynamically allocated integer array.

6.
Define two integers:

int input, result;

These two integers will hold the user's input and the return value of the scanf() function, respectively.

7.
Define three more integers:

int size = 4, i, index = 0;

The size variable will hold the current size of the dynamically growing array (the initial size will be 4 elements). The i variable is a loop counter. The index integer will track the next free element in the dynamically growing array.

8.
Allocate memory for the integer array:

numbers = malloc(size * sizeof(int));

Since sizeof(int) is 4 on most machines and the program starts with 4 elements, this will allocate a memory block of 16 bytes.

Again, note the missing check for the NULL return address. You must always add checks for NULL pointers in your programs; only authors of C books may omit them in their examples...

9.
Enter an infinite while loop:

while (1) {

This loop conditional will always be true, making this an infinite loop. This is desired, however, as the loop should be entered at least once (which requires a true condition) and the loop itself includes an escape mechanism (Step 11).

10.
During every loop iteration, prompt the user for input and read a number using scanf():

printf("Enter a non-zero integer
 (or 0 to quit): ");
result = scanf("%d", &input);

There is no in the prompt string because we want the number entered by the user to line up with the prompt and not on a new line (Figure 10.7).

Figure 10.7. The user is prompted, indicating what kind of information is requested (a nonzero integer) and how to stop the process (by entering 0).


You have seen scanf() before. The format string states to look for an integer number in the user's input. If found, scanf() stores it in the integer variable input, whose address is passed as an argument. The scanf() function returns the number of items successfully read from the user as its result, and that number gets stored into the result variable for a check in the next step.

11.
Check both the scanf() result and the input value, breaking out of the infinite while loop if either one is 0:

if (result < 1 || input == 0) break;

If result is 0, it means that the user has entered a non-numeric character that did not satisfy the %d match pattern given to scanf() in Step 10. This would apply if the user entered a letter, for example. If input is 0, it means that the user did input a number, but it was 0.

Both cases mean that the user has indicated there are no more numbers to enter so the break statement will exit the while loop. The double pipe (which represents OR) is used to say that if either of those conditions is true, the entire condition is true.

12.
Check if there is room left in the dynamically allocated integer array:

if (index == size) {

index is the array index of the element in which the number that was just read has to be stored. size is the current number of available elements in the array. If index is equal to the current size, the array is already too small because index starts at 0 and the array element with that index does not yet exist.

In this case, the array has to be resized, which is what happens in the if body. If there is still enough room (if index does not equal size), the array stays the way it is and execution proceeds in Step 15.

13.
Add the code for resizing the memory block:

size *= 1.5;
numbers = realloc(numbers, size *
 sizeof(int));
printf("Reallocation of the memory
 block. The size is now %d
 bytes.
", size);

The first line recalculates the value of size by making it 50 percent bigger. The second line reallocates memory, increasing the reserved block from its current size to size * sizeof(int) bytes. For safety's sake, you should assign the returned realloc() value to another pointer and validate that (see the sidebar) rather than directly assign this to numbers. In the interest of simplicity, this step has been omitted.

Remember, the size variable keeps track of the number of array elements, but realloc() operates in bytes, so you have to multiply the new size value times sizeof(int).

Finally, a message is printed letting you know that reallocation has taken place.

14.
Close the if body:

}

15.
Store the number entered by the user in the memory block:

numbers[index] = input;

This will store the inputted number in the next free array element, whether or not the array was just resized.

Note how you are using the array subscript operator even though numbers is not an array name but actually a pointer variable.

16.
Increment the array index for the next number:

index++;

17.
Close the while loop body:

}

18.
Sort the numbers in the array:

qsort(numbers, index, sizeof(int),
 sort);

At this point the while loop has terminated and there is a set of numbers stored in memory. Those are sorted using the standard C library's qsort() function. See the documentation for your C library for more information about its usage.

19.
Print the sorted numbers:

for (i = 0; i < index; i++) {
 printf("The value indexed at %d
 is %d.
", i, numbers[i]);
}

This final loop prints the sorted array of numbers. It uses the i counter variable, counting up to index, the number of items stored in the array.

20.
Return the memory block to the pool using the free() function:

free(numbers);
numbers = NULL;

21.
Complete the main function:

    getchar();
    getchar();
    return 0;
}

22.
Add the sort() function:

int sort(const void *x, const void
 *y) {
    const int *a = x, *b = y;
    if (*a < *b) {
            return -1;
    } else if (*a > *b) {
            return 1;
    }
    return 0;
}

The sort() function assists the standard C library's qsort() function in sorting the array. It is called repeatedly with two addresses of two values of the array.

Its job is to compare the two values stored at those addresses and then return -1, 0, or 1 depending on whether the first value is smaller than, equal to, or larger than the second value.

Again, see the documentation of your C library for more details.

23.
Save the file as resize.c, compile, and debug as necessary.

24.
Run the application (Figure 10.8).

Figure 10.8. The program can sort an arbitrary amount of numbers.


Enter a few numbers, and then enter 0. You should get a sorted list of your numbers, and if you type enough numbers you should get occasional messages reporting that the array was just resized (Figure 10.9).

Figure 10.9. If more numbers are entered than there is currently room for, more memory is requested. There is no way to do what this program does without dynamic memory.


✓ Tip

  • With realloc(), you can not only grow but also shrink a block of memory to a smaller size than the initial value.

  • The realloc() function is a valuable tool that can help you keep fixed limits out of your code. It might take a bit more work to use it than an array whose maximum size you estimate when you write your program, but you will never have to go back to change and recompile the code because your guess about the space requirement for some array was wrong.

  • If you do use realloc(), make sure that you always keep the correct current size of the memory block around in a variable (for which the above example uses the size variable). You need this to check your space requirements during program execution and to trigger the expansion of the block as required. You cannot use sizeof() or any other portable tool to get the current size of a block.


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

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