Checking return values for errors, a matter of style?

M

Morris Dovey

Keith Thompson (in (e-mail address removed)) said:

| Out of curiosity, how often do real-world programs really do
| something fancy in response to a malloc() failure?

I don't a definitive answer to this one; but will guess that there are
more than one would be inclined to guess - about a third of the
systems I've worked on over the last 20 years have had recovery
strategies for dealing with this problem.

| The simplest solution, as you say, is to immediately abort the
| program (which is far better than ignoring the error). The next
| simplest solution is to do some cleanup (print a coherent error
| message, flush buffers, close files, release resources, log the
| error, etc.) and *then* abort the program.

| I've seen suggestions that, if a malloc() call fails, the program
| can fall back to an alternative algorithm that uses less memory.
| How realistic is this? If there's an algorithm that uses less
| memory, why not use it in the first place? (The obvious answer:
| because it's slower.) Do programmers really go to the effort of
| implementing two separate algorithms, one of which will be used
| only on a memory failure (and will therefore not be tested as
| thoroughly as the primary algorithm)?

I'm not sure about "alternative algorithms"; but I have an application
that efficiently "recycles" the malloc()ed allocations, rather than
free()ing them. If it decides it's using too much of the available
memory (or if the workload exceeds a threshold), the application
presents a request to a supervisory node for cloning (starting same
application on a new node) and transfers a portion of its database to
be handled by the new node.

Some systems are designed to operate in multiple modes - for example,
it's not unusual for launch vehicles to have a "boost" mode, in which
processing essential to sub-orbital flight is given highest priority
for all available resources, an "orbital" mode in which data
acquisition is given the highest priority, a "dump" mode in which
activities related to data transmission are given highest priority,
and perhaps "diagnostic" and "maintenance" modes. Resource
allocation/retention strategy is (re)configured by a mode change.

Other systems incorporate selective degradation in which priorities
are managed over a spectrum and resource allocations are made (and
retained) according to the current priority. A low-priority processing
unit may be asked to surrender a portion (or all) the resouces it
currently "owns".

Choice of approach pretty much depends on the functional requirements.
 
D

Dave Vandervies

Richard Heathfield said:
The response to a failure depends on the situation. I've covered this in
some detail in my one-and-only contribution to "the literature", so I'll
just bullet-point some possible responses here:

* abort the program. The "student solution" - suitable only for high school
students and, perhaps, example programs (with a big red warning flag).
* break down the memory requirement into two or more sub-blocks.
* use less memory!
* point to a fixed-length buffer instead (and remember not to free it!)
* allocate an emergency reserve at the beginning of the program
* use virtual memory on another machine networked to this one (this
is a lot of work, but it may be worth it on super-huge projects)

* Leave the module's internal structures in a sane state and return the
partial results you've built so far

For the system I work with at my day job, memory allocation failure is
treated as a can't-happen. Any computer we can buy that will provide
enough CPU cycles per unit time will have between a binary order of
magnitude and a decimal order of magnitude more memory than our worst-case
use, so we don't worry too much about how we handle allocation failures;
if malloc starts failing, it's a symptom of much worse problems - both
the inputs to and whatever happens to the outputs from anything that
uses dynamically allocated memory are likely to be wrong, so there's
not much point in doing anything other than leaving things in a state
we can recover from if the rest of the system recovers without a reboot.

But we still don't write code that crashes if malloc fails.

You wouldn't omit
a check on fopen

I've seen allegedly-stable code that did exactly that.


dave
 
V

v4vijayakumar

Johan said:
I've written a piece of code that uses sockets a lot (I know that
sockets aren't portable C, this is not a question about sockets per
se). Much of my code ended up looking like this:

if (function(socket, args) == -1) {
perror("function");
exit(EXIT_FAILURE);
}

I feel that the ifs destroy the readability of my code. Would it be
better to declare an int variable (say succ) and use the following
structure?

int succ;

succ = function(socket, args);
if (succ == -1) {
perror("function");
exit(EXIT_FAILURE);
}

What's considered "best practice" (feel free to substitute with: "what
do most good programmers use")?



all you are going to do is print error message and exit, then you can
as well do this from "function(socket, args)" itself. This means that
you know failure of "function(socket, args)" can not be properly
handled by its users (programmers?!). Returning -1 means that giving a
chance to them to handle it.

following example can be useful.

function(socket, args) sets proper values for global variable "errno2"
in case of any failures. Then, another function, say, rerrq (report
error and quit) reports error and quits from program. like,

function(socket, args);
rerrq("function error ");

rerrq might look like this,

void rerrq(const char *msg)
{
if(errno) /* any system errors? */
perror(msg);
else if(errno2) /* any other errors */
printf("%s: %s\n", msg, errmsg());
else
return; /* no errors */

exit(1);
}

'errmesg()' returns proper string for errno2.
 
A

av

#define FMALLOC(ptr,size) (ptr=malloc (size) ? ptr : exit (-1))
...

for sure i don't understand well
But is it not "void exit(int);"? Did the above compile?
i think it is better

void* fmalloc(int size)
{void *ptr;
if(size<0 || (ptr=malloc(size))==0)
{printf("\nMemory error: exit...\n");
if( eventualy_some_thing_to_do_if_ungly_error()==0 )
goto la;
exit(1);
}
la:;
return ptr;
}

use fmalloc() for normal use
use malloc() if fmalloc() can not handle something
char *p; FMALLOC(ptr, sizeof *p * q)
...
Tracking UB is a bloody nightmare for the maintainer!!!
Being unable to reproduce the bug is morale-killer.

[1] When your 0.02% or whatever finally comes up.

goose,
 
C

Chris Torek

... here is a slightly modified (incomplete) function
that I wrote (during the course of play:) in the last 30 minutes.

The macros ERROR and DIAGNOSTIC are (currently) identical
and merely print the message to screen (with filename, line
number and function name):

----------------------
#define TEST_INPUT ("test.in")
#define TOK_FOPEN (1)
#define TOK_FERROR (2)
#define TOK_INIT (3)
bool test_token (void)
{
FILE *in = fopen (TEST_INPUT, "r");
jmp_buf handler;
int e, c, counter;

/* All errors caught and handled here */
if ((e = setjmp (handler))!=0) {
switch (e) {

Technically this (assignment to "e") is not valid in ANSI C. You
can simply do:

switch (setjmp(handler)) {

and make "case 0" handle the "normal, no-error" return.

Note, however, that if you use setjmp(), you must make some of
your local variables "volatile" (any that are changed after the
setjmp() call, and referred to after longjmp() returns to the
setjmp()).

["case"s snipped; end of if/setjmp sequence here:]
/* Meat of function */
if (!in) longjmp (handler, TOK_FOPEN);

This, of course, could just be a simple "goto", since the
target of the goto (longjmp is just a heavy-duty goto) is
the current function. Why not just write:

if (!in) goto tok_fopen_error;

?

[more snippage]
if (!token_init ()) longjmp (handler, TOK_INIT);
[snip stuff that calls functions]
if (ferror (in)) {
longjmp (handler, TOK_FERROR);
}

Again, these could just be ordinary local-only "goto"s. You only
need to use longjmp when the idea is to "goto" out of some nested
function -- some function that has been called from here, or called
from something called from here, etc -- back to the original
"setjmp". By eliminating the "local goto" cases from the original
switch, you could probably simplify the whole thing to:

if (setjmp(handler)) {
... handle error from subroutine call ...
}

Not only are "local-only gotos" (much) easier to read and debug,
they are also generally more efficient (not that Standard C ever
makes any efficiency promises). So avoiding longjmp() as much
as possible seems like good idea.
 
G

goose

Chris said:
Technically this (assignment to "e") is not valid in ANSI C.

Why not? I honestly thought that it was valid.

Again, these could just be ordinary local-only "goto"s. You only
need to use longjmp when the idea is to "goto" out of some nested
function -- some function that has been called from here, or called
from something called from here, etc -- back to the original
"setjmp". By eliminating the "local goto" cases from the original
switch, you could probably simplify the whole thing to:

if (setjmp(handler)) {
... handle error from subroutine call ...
}

Not only are "local-only gotos" (much) easier to read and debug,
they are also generally more efficient (not that Standard C ever
makes any efficiency promises). So avoiding longjmp() as much
as possible seems like good idea.

True, although it was all local jumps in this example, I
normally tend to use set/longjmp for when I need to
break out of deeply nested function calls without having
to explicitly release resources at every step of the
way.

goose,
 
C

Chris Torek


The Standard says (about setjmp):

Environmental restriction

[#4] An invocation of the setjmp macro shall appear only in
one of the following contexts:

- the entire controlling expression of a selection or
iteration statement;

- one operand of a relational or equality operator with
the other operand an integer constant expression, with
the resulting expression being the entire controlling
expression of a selection or iteration statement;

- the operand of a unary ! operator with the resulting
expression being the entire controlling expression of a
selection or iteration statement; or

- the entire expression of an expression statement
(possibly cast to void).

[#5] If the invocation appears in any other context, the
behavior is undefined.

The list above excludes ordinary assignment.

For the rationale, you would have to ask committee members. My best
guess is that they were worried about stack-based implementations.

(It would be nice if the C standard raised the bar on implementors
here, saying "setjmp and longjmp have to Just Work", none of this
"can only use setjmp's return value in very particular ways" and
"must use volatile qualifier on local variables" business. GCC
manages the latter by doing actual strcmp()s on function calls, to
see if you have called setjmp(), and if so, jiggle registers around
so that you do not have to mark things "volatile". In fact, the
set of functions handled specially includes "setjmp", "sigsetjmp",
and "savectx" -- so if you name your own function "savectx", it has
weird registers-to-stack behavior that slows it down, but makes it
work[%] even if savectx() acts like setjmp().)

[% Actually there are some bugs here, at least in some versions of
gcc: I had to add "asm" lines to mark the SPARC "%l" registers as
"clobbered" in my kernel, for the SMP implementation fo BSD/OS on
the SPARC.]
 

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

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top