Back to TOC Columns


Questions & Answers

Pete Becker

What About realloc()?

Is there something about realloc() they're not telling us? Why is using fscanf() so tricky? Pete has the answers to these questions, and other valuable insights into the Standard C library.

To ask Pete a question about C or C++, send e-mail to pbecker@wpo.borland.com, use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.


Q

My question concerns dynamic memory allocation.

I've seen several articles written about various methods of handling memory allocation when dealing with arrays that need to be expanded "on the fly." These methods all focus on usage of the calloc and malloc functions. What happened to realloc?

The realloc function seems to fit the bill exactly when increasing or decreasing array size. Granted, it doesn't work with the new operator but it certainly seems to perform just as well as calloc and malloc and with a lot less manipulation when changing array size. Since realloc never seems to be mentioned I wondered if it has some bad attributes.

I have been working with a small database program wherein the database size can be as small as 10 or as large as 5000, with a typical size less than 100. I use realloc to increase the size of the primary database array by one record length each time the user inputs new information. I haven't had any problems. My reference material shows it be ANSI compatible, so it doesn't appear to be the invention of one particular compiler.

Your views would be appreciated, since I'm now concerned that if the experts don't talk about it, that means realloc has a "dark side."

-- Bill Heineman

A

There's nothing wrong with realloc; go ahead and use it. Keep in mind, as you mentioned, that the malloc, calloc, realloc, and free family get along with the new and delete family about as well as the Hatfields and the McCoys. Don't intermix them if you value your life.

With that caution out of the way, let's take a fairly detailed look at what realloc can do. It may well be more powerful than you realize.

The most common use of realloc is to change the size of an array allocated with malloc or calloc. As you mentioned, this is often in response to user input. For example:

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

int main()
{
int *ptr = NULL;
int count = 0;
int done = 0;
while( !done )
     {
     int val;
     char input[20];
     gets(input);
     sscanf(input,"%d\n",&val);
     if( val == 0 )
          done = 1;
     else
          {
          ptr = realloc(ptr,
              ++count*sizeof(int));
          ptr[count-1] = val;
          }
     }
return 0;
}

If you trace through this code, you'll see that it does what I just described: each time through the loop it reads a new number, expands the array, and copies the new number into the array. If the new number is zero the loop terminates without copying the final zero.

Did you notice what happens to ptr the first time through the loop? It is initialized with NULL, then passed as the first parameter to realloc. That's okay: the definition of realloc says that you can pass a NULL pointer and realloc will simply allocate the number of bytes you asked for. On subsequent trips through the loop realloc is called to expand the array for the element that is about to be added.

One of the biggest advantages of using realloc here instead of using malloc, memcpy, and free, is that it might be more efficient. The runtime library knows more about the memory management techniques it uses than you do, and often realloc does not need to actually move the data at all. It can simply expand the block in place, or even simply update its internal data if the block it originally allocated has enough extra room in it.

There are a couple of problems with the way I've used realloc here, however. First, the code never checks for success or failure. If for some reason realloc was not able to allocate more space this code would blithely write the new value through a null pointer, causing your program to do various nasty things occasionally. In many cases, however, this error will appear to be harmless. In fact, odds are that you won't notice an error like this until the day before your product is ready for release.

The solution, of course, is to check whether realloc succeeded. That's easy: realloc returns NULL if it is unable to allocate space. We can simply add this test after the call to realloc:

     else
          {
          ptr = realloc(ptr,
              ++count*sizeof(int));
          if( ptr == NULL )
               {
               printf( "Unable to
                   allocate \n" );
               exit(EXIT_FAILURE);
               }
          ptr[count-1] = val;
          }
     }
return 0;
}


This handles the possibility that realloc won't be able to allocate the needed space. This technique does not deal with the potential memory leaks in this code. You probably noticed that the code never frees the memory that it allocated. In a small program like this it might not be important, but if this code is part of a larger program where it gets called repeatedly, you run the risk of running out of memory. This, too, calls for a simple change to the program. Just free the memory at the end:


     else
          {
          ptr = realloc(ptr,++count*sizeof(int));
          if( ptr == NULL )
               {
               printf( "Unable to allocate space.\n" );
               exit(EXIT_FAILURE);
               }
          ptr[count-1] = val;

}

     }
realloc(ptr, 0);
return 0;
}


Yes, you're reading that right: when you pass a size of 0 to realloc it frees the memory that the pointer points to. I don't recommend such clever coding, however. Stick to free, which won't surprise anyone. I just put it in here to show that it can be done.

But if you think about it a bit more you'll see that there's still a memory leak. If the attempted reallocation fails, the old memory is not freed and the program exits. That's a problem, and it's not quite so easy to fix. Look again at the code that handles the reallocation: we've saved the return from realloc in ptr, overwriting the previous value. If realloc returns NULL we've thrown away the pointer value that we need to release the memory. So we need to add a temporary pointer variable to hold the return from realloc, just in case we need the old value. The code now looks like this:


     else
          {
          int *temp = ptr;
          ptr = realloc(ptr,++count*sizeof(int));
          if( ptr == NULL )
               {
               realloc(temp, 0);
               printf( "Unable to allocate space.\n" );
               exit(EXIT_FAILURE);
               }
          ptr[count-1] = val;
          }
     }
realloc(ptr, 0);
return 0;
}


Now we've got code that's free of memory leaks. It's time to look at the code's overall efficiency. Offhand, calling realloc every time through the loop seems like it might be overkill. While most memory managers are fairly efficient, the most efficient way to reallocate memory is to simply not do it. Unless memory resources are tight enough to justify these continual reallocations, it's better to allocate a bigger block each time an allocation is needed, and reduce the number of times the block must be allocated, like this:


     else
          {
          if( count % 32 == 0 )
               {
               int *temp = ptr;
               ptr =
               realloc(ptr,(count+32)*sizeof(int));
               count++;
               if( ptr == NULL )
                    {
                    realloc(temp, 0);
                    printf( "Unable to allocate
     space.\n" );
                    exit(EXIT_FAILURE);
                    }
               }
          ptr[count-1] = val;
          }
     }
realloc(ptr, 0);
return 0;
}


We've now written a short program with robust memory management using only the functions realloc and free. When we pass a NULL pointer to realloc it acts like malloc. It's all ANSI/ISO conformant, so go ahead and use it.

Q

Recently I wrote some code which seemed to be in accord with the way fscanf is supposed to work, but it didn't function as expected. The file being read in text mode contained an unknown number of doubles, separated by commas, on each line. To lift them off I wanted to use:


while( i = fscanf( infile, "%lf,", array++ ) ) ........

The code performs exactly as expected up to and including the last item to be read. There it converts the last item in accordance with the %lf specification, terminates on the comma-\n mismatch, returns 1 for the item read and leaves the \n as the first character in the input buffer.

However, at the next call to fscanf, instead of finding a specification mismatch and returning zero, for no characters converted, it returns EOF. The FILE struct buffer position pointer points to a zero. Is this consistent with the ANSI Standard for this function or perhaps an idiosyncrasy of the particular Microsoft library I happened to be using?

If this is consistent with the Standard, is there another way to read an arbitrary number of fields without resorting to such kludges as using sscanf followed by strtok and atof to separate out and convert the input fields? As I was writing it occurred to me that using "%f,\n" as a format specifier and ensuring that each line finishes with a comma may well work. I wonder.
-- David Sharman

A

In general, if a language feature isn't working the way you expect it to the first thing you should do is check the manual. You may have misunderstood it. If you check the ANSI Standard, here's what it says about the return value of fscanf:

The fscanf function returns the value of the macro EOF if an input failure occurs before any conversion. Otherwise, the fscanf function returns the number of input items assigned, which can be fewer than provided for, or even zero, in the event of an early matching failure.

That's quite a mouthful, but if we work through the technical terminology in that statement carefully, here's what's going on:

When you call fscanf you specify a file handle, a format string, and a series of addresses of locations into which to read data. A format string for fscanf consists of a sequence of characters. Those characters can be pretty much anything you want, but the interesting categories of characters are whitespace, ordinary characters, and conversion specifications. When the format string contains ordinary characters, the characters in the input stream must exactly match the format string. When the input stream does not match the format string we have a "matching failure." So, for example, if the input file contains the characters "abcd" and the format string consists of the characters "efgh", a matching failure occurs.

Conversion specifications, of course, are a bit more complicated. They begin with the character %, followed by a specific set of possible characters. Those characters usually tell the runtime system what kind of data should be coming in: %d handles signed integers, %f handles doubles, and so on. fscanf reads through the input file and the format string, matching data from the input file with the description contained in the format string. When the format string contains a conversion specification, fscanf reads characters from the input stream until it encounters a character that cannot be part of a conversion to the type indicated by the conversion specification. Then it converts the characters that it read into the appropriate type and stores the resulting value through the corresponding pointer argument.

For example, a format string of "%d" tells fscanf to read input characters that correspond to a valid integer, and after it has read the last character of that integer to store the corresponding integral value in the location that the corresponding argument points to. Here's an example:


int val;
fscanf( stdin, "%d", &val )

This code will read a sequence of characters from stdin (standard input), convert the sequence to an integral value, and store that value in val.

fscanf ignores whitespace at the beginning of the input stream and after each conversion. The input stream can have any sequence of whitespace before the characters that fit the format specification, and fscanf will skip the whitespace characters. If you haven't redirected stdin, you can type any sequence of spaces, tabs, and newlines before typing, say, "123." The same thing applies when the format specification contains ordinary characters as well: any whitespace characters preceding the matching characters in the input stream are skipped.

That's the key to understanding why your code example didn't behave the way you expected it to. When your program has read the last floating-point value from the input file there's a newline left in the file and nothing more. The next time fscanf executes it skips the newline because newline is whitespace. So fscanf finds the end of the file and returns. Since the input stream failed to match the format specifier, and no conversions were performed, fscanf returns EOF. Here's the test your code should be making: instead of testing for a return value of 0, test for a return value of EOF:


while( (i = fscanf( infile, "%lf,",
array++ )) != EOF ) ........


But while we're on the subject, it is intriguing to look into what we would need to do to get a return value of zero. Let's take another look at that quote from the Standard.

The fscanf function returns the value of the macro EOF if an input failure occurs before any conversion. Otherwise, the fscanf function returns the number of input items assigned, which can be fewer than provided for, or even zero, in the event of an early matching failure.

That last sentence in the description of the return value is a bit dense, but here's the gist: scanf first skips leading whitespace, then tries to read a non-whitespace character from the input stream. If there are no characters left in the input stream, scanf returns EOF. If scanf succeeds in reading the character, it compares it to the current character in the format string, and as long as the character it just read matches the format string, it continues reading single characters. If the attempted match fails and the current character in the format string is %, scanf attempts to convert the text following in accordance with the conversion specifier. If it is any other character we have what the standard refers to as an "early matching failure," since scanf has not yet encountered any conversion specifiers. In this case, scanf returns 0. So, with our earlier code snippet,


int val;
fscanf( stdin, "%d", &val )

if you type "abcd" as input there will be no match, and no conversion will have happened; the return value will be zero. Try it:


#include <stdio.h>
int main()
{
     int val;
     int res = fscanf( stdin, "%d",
                       &val );
     printf( "res: %d, val: %d\n",
res, val ); return 0; }


I suspect that I'm like a lot of C programmers: I only use scanf for its conversion capabilities, not for input of formatted text. I learned a lot about it from the explorations that I did in response to this question, and as a result I feel more comfortable with it. I suspect that I'll be using it more in the future. The problem with anything as powerful as scanf is that you need to know how to control that power, and that takes time and study. Now, what can I do with scanf's assignment suppression? o

Pete Becker is Senior Development Manager for C++ Quality Assurance at Borland International. He has been involved with C++ as a developer and manager at Borland for the past six years, and is Borland's principal representative to the ANSI/ISO C++ standardization committee.