xmalloc

M

Malcolm McLean

Continuing the error-handling thread.

I am floating

void *xmalloc(int sz)
{
void *answer;

assert(sz >= 0);
if(sz == 0)
sz = 1;
answer = malloc(sz);
if(!answer)
{
fprintf(stderr, "Can't allocate %d byte%c\n", sz, sz == 1 ? ' ', 's');
exit(EXIT_FAILURE);
}
return answer;
}

as a solution to the malloc() problem.
(Chuck Falconer's suggestion)
You call it for trivial allocations on the basis that if the computer won't
give a few bytes of memory, not much can be done.
However you wouldn't call it to allocate an image, for example, because
legitmate images can be quite large in relation to computer memories.
 
R

Richard Heathfield

Malcolm McLean said:
Continuing the error-handling thread.

I am floating

void *xmalloc(int sz)
{
void *answer;

assert(sz >= 0);
if(sz == 0)
sz = 1;
answer = malloc(sz);
if(!answer)
{
fprintf(stderr, "Can't allocate %d byte%c\n", sz, sz == 1 ? ' ',
's'); exit(EXIT_FAILURE);
}
return answer;
}

as a solution to the malloc() problem.

What malloc() problem? And this "solution" suffers from the same problem
as any other "solution" of the same kind - it's no earthly use in a
library, and libraries are where malloc calls belong.

There are two reasons why it's no earthly use in a library.

For one thing, it displays a message on stderr, which isn't much cop in
a system where stderr messages are ignored - e.g. a Win32 GUI app. But
that's trivial to fix - either remove the message completely, or add a
callback or a pointer to an error code, so that the application can
worry about how to display the message.

The second problem is also quite easy to fix, and it's this - the
function calls exit(), thus taking the decision to quit the program out
of the hands of the programmer. Therefore, the programmer won't call
this function himself, and won't be able to call any other function in
the library that /does/ call it.

Here's my suggested replacement, which incorporates all the fixes I see
as being necessary:

#include <stdlib.h>

void *xmalloc(size_t sz)
{
return malloc(sz);
}

You call it for trivial allocations on the basis that if the computer
won't give a few bytes of memory, not much can be done.

Whether that is true depends on the problem you're trying to solve. For
example, it may well be possible to switch to a static solution which
is perhaps not as fast as the dynamic solution but which will
nevertheless do the Right Thing in a reasonable time.

<snip>
 
M

Malcolm McLean

Richard Heathfield said:
Malcolm McLean said:


What malloc() problem? And this "solution" suffers from the same problem
as any other "solution" of the same kind - it's no earthly use in a
library, and libraries are where malloc calls belong.

There are two reasons why it's no earthly use in a library.

For one thing, it displays a message on stderr, which isn't much cop in
a system where stderr messages are ignored - e.g. a Win32 GUI app. But
that's trivial to fix - either remove the message completely, or add a
callback or a pointer to an error code, so that the application can
worry about how to display the message.

The second problem is also quite easy to fix, and it's this - the
function calls exit(), thus taking the decision to quit the program out
of the hands of the programmer. Therefore, the programmer won't call
this function himself, and won't be able to call any other function in
the library that /does/ call it.

Here's my suggested replacement, which incorporates all the fixes I see
as being necessary:

#include <stdlib.h>

void *xmalloc(size_t sz)
{
return malloc(sz);
}



Whether that is true depends on the problem you're trying to solve. For
example, it may well be possible to switch to a static solution which
is perhaps not as fast as the dynamic solution but which will
nevertheless do the Right Thing in a reasonable time.
In which case would call regular malloc()
However you are doubling the cost of the software with that strategy, all
because a 2GB machine might refuse to give a few hundred bytes of memory. If
it is running a life-support machine, fair enough, but most software
doesn't.

On the other hand you could argue that xmalloc() is a bad idea, because no
library that uses it could ever find its way into a life-support machine.
On the other hand, you could argue that the life support machine is more
likely to blow a fuse than to exit in xmalloc(), so you need a backup
anyway.

I take your point about vandalised stderrs.
Maybe we should put an assert fail in there as well / instead.
 
R

Richard Heathfield

Malcolm McLean said:
In which case would call regular malloc()
However you are doubling the cost of the software with that strategy,
all because a 2GB machine might refuse to give a few hundred bytes of
memory.

The way I see it is this: by writing an inferior program that is
prepared to bomb out at the drop of a hat, I can *reduce* the cost of
writing the program, but it *would be* an inferior program. And then
I'd have to market the program to the kind of people who used to buy
Cortinas, or Edsels. I prefer to deal with Maserati types. :)

<snip>
 
R

Roland Pibinger

The second problem is also quite easy to fix, and it's this - the
function calls exit(), thus taking the decision to quit the program out
of the hands of the programmer. Therefore, the programmer won't call
this function himself, and won't be able to call any other function in
the library that /does/ call it.

You can often see programs that check the return value of malloc but
you hardly (practically never) see any code that actually handles the
OOM (out-of-memory) condition (e.g. frees some pre-allocated memory).
Strange, isn't it?
Moreover, C libraries usually crash when the caller provides
insufficient resources or invalid arguments. In that sense abort() is
preferable to exit() for OOM.
For
example, it may well be possible to switch to a static solution which
is perhaps not as fast as the dynamic solution but which will
nevertheless do the Right Thing in a reasonable time.

The 'static solution' may cause a 'stack' overflow. BTW, how would you
handle the second type of OOM, 'stack' overflow? The same way as OOM
for dynamic memory?
 
E

Eric Sosman

Malcolm said:
[...]
However you are doubling the cost of the software with that strategy,
all because a 2GB machine might refuse to give a few hundred bytes of
memory. [...]

A 2GB machine *will* refuse to allocate a few hundred
bytes of memory -- immediately after a successful allocation
of 2GB. Maybe the program could choose to discard the 2GB
allocation that's just holding a bunch of cache, and try again
on the couple hundred bytes it Really Needs? Too late, Malcolm
has already euthanized it. Requesciat in pacem.
 
R

Richard Heathfield

Roland Pibinger said:

You can often see programs that check the return value of malloc but
you hardly (practically never) see any code that actually handles the
OOM (out-of-memory) condition (e.g. frees some pre-allocated memory).
Strange, isn't it?

Yes. It's a great shame.
Moreover, C libraries usually crash when the caller provides
insufficient resources or invalid arguments. In that sense abort() is
preferable to exit() for OOM.

Well, I do see your point, but personally I'd prefer for the program to
make sensible choices that minimise inconvenience to the user.
The 'static solution' may cause a 'stack' overflow.

So make sure it doesn't, by avoiding stress to your 'stack'.
BTW, how would you
handle the second type of OOM, 'stack' overflow? The same way as OOM
for dynamic memory?

One of the reasons dynamic memory is so useful is that there's a heck of
a lot of it to go around, compared to a typical 'stack' - but the other
great advantage thereof is that you /can/ detect an OOM condition with
malloc, whereas you can't with 'stack' overflow - at least, not
portably. Nevertheless, you can still do quite a lot with a 'stack'
that doesn't involve a significant risk of overflow (the adjective
being necessary only because the C Standard gives us very little in the
way of guarantees about 'stack' capacity - in practice, nowadays it is
only a problem if you treat the 'stack' rather recklessly).
 
E

Eric Sosman

Roland said:
You can often see programs that check the return value of malloc but
you hardly (practically never) see any code that actually handles the
OOM (out-of-memory) condition (e.g. frees some pre-allocated memory).
Strange, isn't it?

Not all that strange. Half the programmers in the world
are below-average.

But if you follow Malcolm's Maxims, even the above-average
programmers will be helpless. Programs that deal with malloc()
failure in a significant way may be a minority, but those that
do it *need* to to do it. Malcolm argues, in essence, that
such programs should not be written in C.
Moreover, C libraries usually crash when the caller provides
insufficient resources or invalid arguments. In that sense abort() is
preferable to exit() for OOM.

Oh, heaven spare us! Not only do you want to kill off
the program, but you also want to prevent it from running
its carefully-registered atexit() handlers? And you don't
care whether buffers are flushed, streams are closed, and
temporary files are removed? What do you do for amusement,
juggle flasks of nitroglycerin while fire-walking?
 
M

Malcolm McLean

Roland Pibinger said:
You can often see programs that check the return value of malloc but
you hardly (practically never) see any code that actually handles the
OOM (out-of-memory) condition (e.g. frees some pre-allocated memory).
Strange, isn't it?
Moreover, C libraries usually crash when the caller provides
insufficient resources or invalid arguments. In that sense abort() is
preferable to exit() for OOM.
Normally if you've written the program in such a way that it makes a lot of
trival allocations, there is nothing you can do on failure, short of calling
a back-up routine that uses fixed buffers.
So you handle fail conditions for the few large legitimate allocations,
which might run the computer out of memory, and abort on the rest.

You can declare a global "world" pointer, and then in atexit try to save the
state of the program, but there are two problems. Firstly if the OS won't
give you a few hundred bytes, it might well not open files for you either.
Secondly, all the code has to be very carefully written so that an
allocation failure never causes the state to lose coherence. Otherwise you
will crash when you try to write a null pointer, or worse, write an image
that looks right but is in fact subtly corrupted.

So there really isn't an easy answer.
The 'static solution' may cause a 'stack' overflow. BTW, how would you
handle the second type of OOM, 'stack' overflow? The same way as
OOM for dynamic memory?
So if you use recursive subroutines you've already got one potential source
of crashes on malicious or excessive input. That reduces the motivation to
fix malloc(), because you can't guarantee correct operation anyway.
 
I

Ian Collins

Richard said:
Malcolm McLean said:

Should be size_t.
What malloc() problem? And this "solution" suffers from the same problem
as any other "solution" of the same kind - it's no earthly use in a
library, and libraries are where malloc calls belong.

It could be enhanced by providing a (cough) global failure function, say
xmalloc_failure, that gets called when malloc returns NULL:

void* default_xmalloc_failure(size_t s) {
assert(!"Can't allocate memory"); return NULL;}

void* (*xmalloc_failure)(size_t) = &default_xmalloc_failure;

The xmalloc becomes

void *xmalloc(size_t sz)
{
void *answer = malloc( sz == 0 ? 1 : sz );
if(answer == NULL)
{
xmalloc_failure(sz);
}
return answer;
}

Which gives the user control over what happens when malloc fails.
 
R

Richard Heathfield

Ian Collins said:

It could be enhanced by providing a (cough) global failure function,
say xmalloc_failure, that gets called when malloc returns NULL:

void* default_xmalloc_failure(size_t s) {
assert(!"Can't allocate memory"); return NULL;}

void* (*xmalloc_failure)(size_t) = &default_xmalloc_failure;

The xmalloc becomes

void *xmalloc(size_t sz)
{
void *answer = malloc( sz == 0 ? 1 : sz );
if(answer == NULL)
{
xmalloc_failure(sz);
}
return answer;
}

Which gives the user control over what happens when malloc fails.

It's an improvement, but it's still not good enough as a general
solution. Consider this code:

foo *foo_create(size_t n)
{
static const foo fblank;
foo *new = malloc(sizeof *new); /* A */
if(new != NULL)
{
*new = fblank;
new->bar = malloc(n * sizeof *new->bar); /* B */
if(new->bar == NULL)
{
free(new);
new = NULL;
}
else
{
static const t_bar bblank;
while(n--)
{
new->bar[n] = bblank;
}
}
}
return new;
}

If we were to rewrite this to use your suggestion, that would be fine
for the malloc at A, but if the malloc at B fails, we have to clean up,
and that means that the cleanup code needs access to new. Your solution
doesn't address that problem.
 
M

Malcolm McLean

Richard Heathfield said:
Ian Collins said:

It could be enhanced by providing a (cough) global failure function,
say xmalloc_failure, that gets called when malloc returns NULL:

void* default_xmalloc_failure(size_t s) {
assert(!"Can't allocate memory"); return NULL;}

void* (*xmalloc_failure)(size_t) = &default_xmalloc_failure;

The xmalloc becomes

void *xmalloc(size_t sz)
{
void *answer = malloc( sz == 0 ? 1 : sz );
if(answer == NULL)
{
xmalloc_failure(sz);
}
return answer;
}

Which gives the user control over what happens when malloc fails.

It's an improvement, but it's still not good enough as a general
solution. Consider this code:

foo *foo_create(size_t n)
{
static const foo fblank;
foo *new = malloc(sizeof *new); /* A */
if(new != NULL)
{
*new = fblank;
new->bar = malloc(n * sizeof *new->bar); /* B */
if(new->bar == NULL)
{
free(new);
new = NULL;
}
else
{
static const t_bar bblank;
while(n--)
{
new->bar[n] = bblank;
}
}
}
return new;
}

If we were to rewrite this to use your suggestion, that would be fine
for the malloc at A, but if the malloc at B fails, we have to clean up,
and that means that the cleanup code needs access to new. Your solution
doesn't address that problem.
How about this

void *xmalloc(size_t sz)
{
void *answer;

if(sz == 0)
sz = 1;
while( !(answer = malloc(sz))
(*malloc_failure(sz));

return answer;
}
 
I

Ian Collins

Richard said:
Ian Collins said:

It could be enhanced by providing a (cough) global failure function,
say xmalloc_failure, that gets called when malloc returns NULL:

void* default_xmalloc_failure(size_t s) {
assert(!"Can't allocate memory"); return NULL;}

void* (*xmalloc_failure)(size_t) = &default_xmalloc_failure;

The xmalloc becomes

void *xmalloc(size_t sz)
{
void *answer = malloc( sz == 0 ? 1 : sz );
if(answer == NULL)
{
xmalloc_failure(sz);
}
return answer;
}

Which gives the user control over what happens when malloc fails.

It's an improvement, but it's still not good enough as a general
solution. Consider this code:

foo *foo_create(size_t n)
{
static const foo fblank;
foo *new = malloc(sizeof *new); /* A */
if(new != NULL)
{
*new = fblank;
new->bar = malloc(n * sizeof *new->bar); /* B */
if(new->bar == NULL)
{
free(new);
new = NULL;
}
else
{
static const t_bar bblank;
while(n--)
{
new->bar[n] = bblank;
}
}
}
return new;
}

If we were to rewrite this to use your suggestion, that would be fine
for the malloc at A, but if the malloc at B fails, we have to clean up,
and that means that the cleanup code needs access to new. Your solution
doesn't address that problem.
Then you have to turn up the complexity another notch an introduce a
cleanup handler stack, which just about takes us back to checking each
return of malloc()!

This situation is one of the reasons I like that unmentionable in these
parts cousin of C with its exceptions and destructors!
 
R

Roland Pibinger

Not all that strange. Half the programmers in the world
are below-average.

which is at least half-true.
But if you follow Malcolm's Maxims, even the above-average
programmers will be helpless. Programs that deal with malloc()
failure in a significant way may be a minority, but those that
do it *need* to to do it. Malcolm argues, in essence, that
such programs should not be written in C.

The discussion basically boils down to one question: Is OOM an error
that reasonably can and should be handled by the application or is it
a fatal error?
The 'fatal error' advocates have already shown how they tackle the
problem. Now it's time for the other camp to demonstrate how OOM can
be consistently _handled_ throughout the program ('return NULL;' is
not enough).
 
R

Roland Pibinger

Normally if you've written the program in such a way that it makes a lot of
trival allocations, there is nothing you can do on failure, short of calling
a back-up routine that uses fixed buffers.
So you handle fail conditions for the few large legitimate allocations,
which might run the computer out of memory, and abort on the rest.

The main cause of OOM are memory leaks, i.e. bugs, that cannot be
handled anyway. abort() is more dramatic than exit(). It indicates
that there is a problem and some action ought to be performed now.
 
K

Keith Thompson

The discussion basically boils down to one question: Is OOM an error
that reasonably can and should be handled by the application or is it
a fatal error?
The 'fatal error' advocates have already shown how they tackle the
problem. Now it's time for the other camp to demonstrate how OOM can
be consistently _handled_ throughout the program ('return NULL;' is
not enough).

Who says it can be handled *consistently*?
 
M

Malcolm McLean

Keith Thompson said:
Who says it can be handled *consistently*?
That is part of the issue.
malloc() return NULL. The if condition following the malloc() is like a
yippy dog chasing a car. Now it's got an error, what is it going to do with
it?
 
E

Eric Sosman

Roland said:
which is at least half-true.


The discussion basically boils down to one question: Is OOM an error
that reasonably can and should be handled by the application or is it
a fatal error?
The 'fatal error' advocates have already shown how they tackle the
problem. Now it's time for the other camp to demonstrate how OOM can
be consistently _handled_ throughout the program ('return NULL;' is
not enough).

Already mentioned a few times in this thread:

buff = malloc(image_size);
if (buff == NULL) {
fprintf (stderr, "Image too large (%lu) to paste\n",
(unsigned long)image_size);
return;
}
/* read image into buff, insert in current document */

Here's another I think has been referred to:

must_have_mem = malloc(how_much);
if (must_have_mem == NULL) {
fprintf (stderr, "Out of memory; shutting down\n");
save_snapshot(snapshot_file);
exit (EXIT_FAILURE);
}
/* store "must have" data in allocated memory */

And here's still another (I don't remember whether it's
cropped up in this thread yet):

void *getmem(size_t bytes) {
void *new = malloc(bytes);
if (new == NULL && bytes > 0) {
fprintf (stderr, "Failed to allocate %lu bytes\n",
(unsigned long)bytes);
free (emergency_stash);
emergency_stash = NULL;
new = malloc(bytes);
if (new == NULL) {
fprintf (stderr, "You were warned ...!\n");
exit (EXIT_FAILURE);
}
fprintf (stderr, "Running on fumes: save your work "
"and exit soon!\n");
}
return NULL;
}

Even if out-of-memory is a "fatal error," it does not follow
that the program should have no opportunity to "die with dignity."
Have you made a will, Roland? If so, should the fact that many
people die intestate invalidate your will? If not, I certainly
don't want your intestacy to invalidate my will!
 
E

Eric Sosman

Malcolm said:
Normally if you've written the program in such a way that it makes a lot
of trival allocations, there is nothing you can do on failure, short of
calling a back-up routine that uses fixed buffers.
So you handle fail conditions for the few large legitimate allocations,
which might run the computer out of memory, and abort on the rest.

That is so bass-ackwards it boggles the mind.

Here's the question: Does your program have in-memory state
that is valuable? If so, the proximate cause of the program's
trouble is irrelevant; what matters is the preservation of that
valuable state.
You can declare a global "world" pointer, and then in atexit try to save
the state of the program, but there are two problems.

... one being that abort doesn't run atexit callbacks ...
Firstly if the OS
won't give you a few hundred bytes, it might well not open files for you
either. Secondly, all the code has to be very carefully written so that
an allocation failure never causes the state to lose coherence.
Otherwise you will crash when you try to write a null pointer, or worse,
write an image that looks right but is in fact subtly corrupted.

So there really isn't an easy answer.

Let's avoid all hard problems; they demoralize the weak.
So if you use recursive subroutines you've already got one potential
source of crashes on malicious or excessive input. That reduces the
motivation to fix malloc(), because you can't guarantee correct
operation anyway.

And the janitor might unplug the computer to plug in the
floor polisher, or the fire sprinklers might open up and flood
the motherboard, or a meteorite might smash the computer to
tiny bits, or the Borg might teleport it away for assimilation.
Does the existence of Failure Mode A dissuade you from taking
any precautions against Failure Mode B? "I don't wear seat
belts in cars, because avian flu might kill me anyhow." Pfui!
 
S

SM Ryan

(e-mail address removed) (Roland Pibinger) wrote:
# On Sat, 23 Jun 2007 16:30:12 -0400, Eric Sosman wrote:
# > Not all that strange. Half the programmers in the world
# >are below-average.
#
# which is at least half-true.

Presumes a normal distribution. While many processes show normal
distribution of results, many processes also do not. Human
populations which are deliberately selected for some trait can
be signficantly skewed.

Hidden assumptions--the claymore mines of programming.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top