Hello clc,
I have a buffer in a program which I write to. The buffer has
write-only, unsigned-char-at-a-time access, and the amount of space
required isn't known a priori. Therefore I want the buffer to
dynamically grow using realloc().
A comment by Richard Heathfield in a thread here suggested that a good
algorithm for this is to use realloc() to double the size of the buffer,
but if realloc() fails request smaller size increments until realloc()
succeeds or until realloc() has failed to increase the buffer by even
one byte.
The basic idea is below. The key function is MyBuffer_writebyte(), which
expects the incoming MyBuffer object to be in a consistent state.
Are there any improvements I could make to this code? To me it feels
clumsy, especially with the break in the 3-line while loop.
struct mybuffer_t {
unsigned char *data;
size_t size; /* size of buffer allocated */
size_t index; /* index of first unwritten member of data */
};
typedef struct mybuffer_t MyBuffer;
void MyBuffer_writebyte(MyBuffer *buf, unsigned char byte) {
if(buf->size == buf->index) {
/* need to allocate more space */
size_t inc = buf->size;
unsigned char *tmp;
while(inc>0) {
tmp = realloc(buf->data, buf->size + inc);
if (tmp!=NULL) break; /* break to preserve the size of inc*/
inc/=2;
}
if(tmp==NULL) {
/* couldn't allocate any more space, print error and exit */
exit(EXIT_FAILURE);
Nobody mentioned this so far, but I think it's worth mentioning.
Your immediate above comment is wrong. The exit() function does not
necessarily print an error. In this case, the exit() function
terminates the program and, according to the C standard, allowably
silently.
In fact, most implementations I've come across don't print anything
out as a result of calling the exit() function--the program simply
terminates silently, and the user is left waiving his or her hands in
the air.
And even if exit() did print out an error, what would you expect for
it to print out in this case? Surely it can't print out the following
(unless you expect C to have a crystal ball that intelligently reads
comments):
couldn't allocate any more space, print error and exit
If you want to print an error and then "exit()", you'll have to print
the error out on your own. Something like this would work:
if ( !tmp )
{
/* couldn't allocate any more space, print error and exit */
fprintf(stderr, "couldn't allocate any more space\n");
exit(EXIT_FAILURE);
}
If you do something like the above, make sure you include <stdio.h>
for the prototype for fprintf() and <stdlib.h> for the prototype for
exit() and the definition of the macro EXIT_FAILURE.
One of the problems with outputting an error message to stderr (or
stdout) and then calling exit() is that your user may never see the
error message. There is nothing in the C standard that prevents an
operating system, or more appropriately, a run time environment, from
terminating your program when exit() is called without you having a
glimmer of a chance of viewing the message output to stderr (or
stdout).
When you decide to call the function exit(), you are basically, as a
programmer, throwing your hands up in the air (and most likely to have
the user emulate you) and claiming that "this condition should never
happen". After all, the exit() function simply terminates the program
as far as the user is concerned.
Given this, perhaps you should consider some alternatives to using or
not using exit(). Far better as an alternative, IMHO, is to use the
assert macro instead of the exit() function. As a compromise, use the
assert macro in conjunction with the exit() function. For example:
assert(tmp != NULL);
if ( !tmp )
{
exit(EXIT_FAILURE);
}
If you use the assert macro, make sure you include <assert.h>.
As a developer, you should know to compile and test your code without
the NDEBUG macro defined. That way, the assert macro will fire off
before your program even gets to the exit() statement. Furthermore,
the assert macro will hopefully and most likely provide you with
valuable information that helps you to trace the root cause of your
problem, which is most likely, IMHO, a programming error. (If you're
really lucky, you'll be able to break into a debugger when the assert
macro fires off. Visual Studio 98 and later provide this feature,
BTW.)
Note that even with the added assert statement, you can get back to
your original functionality of calling only exit() and not assert'ing
simply by defining the macro NDEBUG; this is well defined by the C
standard.
On a somewhat related note, you should NEVER call the exit() function
from main(). Doing so expresses your lack of knowledge of Standard C,
which guarantees that a return statement in main() has the same effect
as calling exit() with an argument that is the same as the return
value. In other words, use only return statements in main()--never
call exit() from main().
And finally, the only acceptable return values from main, and the only
values you can pass into exit(), are 0, EXIT_SUCCESS and EXIT_FAILURE.
The latter two macros are defined in <stdlib.h>, so make sure to
include that header file if you use either one. Returning a value of 0
or calling exit(0) is equivalent to returning a value of EXIT_SUCCESS
or calling exit(EXIT_SUCCESS), as far as the C standard is concerned.
One convention I've grown accustomed to is to return 0 from main() if
I never return a failure condition, e.g.:
int main(void)
{
return 0;
}
But I return EXIT_SUCCESS as a successful return value if I also
return a failure condition (which can only be EXIT_FAILURE and nothing
else) from main(), e.g.:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if ( argc < 2 )
{
printf("Error.\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
The above could arguably be better written as (along with many other
variants):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int status = EXIT_SUCCESS;
if ( argc < 2 )
{
printf("Error.\n");
status = EXIT_FAILURE;
}
return status;
}
Best regards