views:

684

answers:

12

I come from a C# background but I'm currently learning C atm. In C#, when one wants to signal that an error has occurred, you throw an exception. But what do you do in C ?

Say for example you have a stack with push and pop functions. What is the best way to signal that the stack is empty during a pop ? What do you return from that function?

double pop(void)
{
 if(sp > 0)
  return val[--sp];
 else {
  printf("error: stack empty\n");
  return 0.0;
 }
}

K&R's example from page 77 (code above) returns a 0.0...but what if the used pushed a 0.0 earlier on the stack...how do you know whether the stack is empty or whether a correct value was returned ?

+3  A: 

One approach is to specify that pop() has undefined behaviour if the stack is empty. You then have to provide an is_empty() function that can be called to check the stack.

Another approach is to use C++, which does have exceptions :-)

anon
More usefully for this particular case, C++ has a stack right there in the library :-)
Steve Jessop
But the C++ std::stack pop function does not actually do what the OP wants.
anon
True, and understanding why will add to the OP's C++ education, which is the main purpose of the question :-) Anyway, wrapping one call each to `top()` and `pop()`, returning a copy, gives the same end result as taking what the OP has and applying what you say about needing an `empty()` function. IYSWIM.
Steve Jessop
+13  A: 

Exception-like behavior in C is accomplished via setjmp/longjmp. However, what you really want here is an error code. If all values are potentially returnable, then you may want to take in an out-parameter as a pointer, and use that to return the value, like so:

int pop(double* outval)
{
        if(outval == 0) return -1;
        if(sp > 0)
                *outval = val[--sp];
        else {
                printf("error: stack empty\n");
                return -1;
        }
        return 0;
}

Not ideal, obviously, but such are the limitations of C.

Also, if you go this road, you may want to define symbolic constants for your error codes (or use some of the standard ones), so that a user can distinguish between "stack empty" and "you gave me a null pointer, dumbass".

Tyler McHenry
I disagree, somewhat, because even if I get what you mean I would not give someone coming from java/c# land the assumption that setjmp/longjmp is in any way the 'solution' to 'where is my exception?'
Jonke
Note that very little C code uses setjmp/longjmp for historical reasons. Error codes are the usual C way. Error codes aren't very good; the lack of Exceptions in C is a major reason why more modern languages are better.
Nelson
ANSI has codified `setjmp' so it is guaranteed to work (at least if thecompiler conforms to the standard), but stack switch and concurrency havenot been standardized, so it is still a problem to write a safe threadspackage for C (even using asm to do the context switch) because a compiler*could* (though it might be unlikely) perform optimizations and transformsthat break assumptions in the threads package.
Jonke
Jonke is right - setjmp/longjmp simulates only one small part of throwing an exception. In the face of the bizarre control flow that results from this, you need the ability to write exception-safe code, and for that you need destructors (or try/finally). Without that, the only manageable approach is error-code return values.
Daniel Earwicker
I think so. The real question is "In C, how does a function indicate an error if all possible return values are valid?". The poster just assumed that the answer was some form of exception because he is coming from C#.
Tyler McHenry
well actually since I knew there are no exceptions in C#, I asked for the suggested way to go about signalling errors
Andreas Grech
Tyler McHenry
@Dreas Grech: Huh? There are most certainly exceptions in C#... I agree with some of the others here; just because you know how to do something in one language does not mean that is how it is done everywhere. C is not Java; use return codes, that is the C way.
Ed Swangren
no no sorry, i meant C. that "C#" in my previous comment was a typo
Andreas Grech
@Ed Swangren: "just because you know how to do something in one language does not mean that is how it is done everywhere" <= exactly my point of asking this question, no ?
Andreas Grech
A: 

you can return a pointer to double:

  • non-NULL -> valid
  • NULL -> invalid
dfa
downvotes without comments are pointless, please explain your downvotes
dfa
I'm not the downvote, but I would wonder where the backing storage for the pointer is coming from. If its the popped element, then the caller has to dereference it before a new value can be pushed. If its a separate `static double`, then the caller has to dereference before the next call to pop. Both cause a lot of trouble for the calling code.
RBerteig
I didn't downvote either but I had the same concerns. I think it's an effective approach but you'd need to change how the function works and stores data to do it.
Jon
+6  A: 

You have a few options:

1) Magic error value. Not always good enough, for the reason you describe. I guess in theory for this case you could return a NaN, but I don't recommend it.

2) Define that it is not valid to pop when the stack is empty. Then your code either just assumes it's non-empty (and goes undefined if it is), or asserts.

3) Change the signature of the function so that you can indicate success or failure:

int pop(double *dptr)
{
    if(sp > 0) {
            *dptr = val[--sp];
            return 0;
    } else {
            return 1;
    }
}

Document it as "If successful, returns 0 and writes the value to the location pointed to by dptr. On failure, returns a non-zero value."

Optionally, you could use the return value or errno to indicate the reason for failure, although for this particular example there is only one reason.

4) Pass an "exception" object into every function by pointer, and write a value to it on failure. Caller then checks it or not according to how they use the return value. This is a lot like using "errno", but without it being a thread-wide value.

5) As others have said, implement exceptions with setjmp/longjmp. It's doable, but requires either passing an extra parameter everywhere (the target of the longjmp to perform on failure), or else hiding it in globals. It also makes typical C-style resource handling a nightmare, because you can't call anything that might jump out past your stack level if you're holding a resource which you're responsible for freeing.

Steve Jessop
+1: For point #3.
Anthony Cuozzo
+9  A: 

You could build an exception system on top of longjmp/setjmp: Exceptions in C with Longjmp and Setjmp. It actually works quite well, and the article is a good read as well. Here's how your code could look like if you used the exception system from the linked article:

  TRY {
    ...
    THROW(MY_EXCEPTION);
    /* Unreachable */
  } CATCH(MY_EXCEPTION) {
    ...
  } CATCH(OTHER_EXCEPTION) {
    ...
  } FINALLY {
    ...
  }

It's amazing what you can do with a little macros, right? It's equally amazing how hard it is to figure out what the heck is going on if you don't already know what the macros do.

longjmp/setjmp are portable: C89, C99, and POSIX.1-2001 specify setjmp().

Note, however, that exceptions implemented in this way will still have some limitations compared to "real" exceptions in C# or C++. A major problem is that only your code will be compatible with this exception system. As there is no established standard for exceptions in C, system and third party libraries just won't interoperate optimally with your homegrown exception system. Still, this can sometimes turn out to be a useful hack.

I don't recommend using this in serious code which programmers other than yourself are supposed to work with. It's just too easy to shoot yourself in the foot with this if you don't know exactly what is going on. Threading, resource management, and signal handling are problem areas which non-toy programs will encounter if you attempt to use longjmp "exceptions".

Ville Laurikari
I actually built something like this in C++ prior to exceptions becoming widely available. I even implemented by own form of stack unwinding. Luckily, I came to my senses before we used it in production code.
anon
@Neil: I liked the second part of that sentence :-)
jeroenh
"Luckily, I came to my senses". Congratulations. Symbian did the same thing you did, right up to the point where you came to your senses, and they shipped. 10+ years later, they still have NewLC everywhere...
Steve Jessop
+1  A: 

There is no equivalent to exceptions in straight C. You have to design your function signature to return error information, if that's what you want.

The mechanisms available in C are:

  • Non-local gotos with setjmp/longjmp
  • Signals

However, none of these has semantics remotely resembling C# (or C++) exceptions.

Paul Lalonde
+2  A: 

In cases such as this, you usually do one of

  • Leave it to the caller. e.g. it's up to the caller to know if it's safe to pop()(e.g. call a stack->is_empty() function before popping the stack), and if the caller messes up, it's his fault and good luck.
  • Signal the error via an out parameter, or return value.

e.g. you either do

double pop(int *error)
{
  if(sp > 0) {
      return val[--sp];
      *error = 0;
  } else {
     *error = 1;
      printf("error: stack empty\n");
      return 0.0;
  }

}

or

int pop(double *d)
{
   if(sp > 0) { 
       *d = val[--sp];
       return 0;
   } else {
     return 1;

   }
}
nos
A: 

There are already some good answers here, just wanted to mention that something close to "exception", can be done with the use of a macro, as been done in the awesome MinUnit (this only returns the "exception" to the caller function).

Liran Orevi
A: 

1) You return a flag value to show it failed, or you use a TryGet syntax where the return is a boolean for success while the value is passed through an output parameter.

2) If this is under Windows, there is an OS-level, pure C form of exceptions, called Structed Exception Handling, using syntax like "_try". I mention it, but I do not recommend it for this case.

Steven Sudit
+2  A: 

this actually is a perfect example of the evils of trying to overload the return type with magic values and just plain questionable interface design

one solution I might use to eliminate the ambiguity(and thus the need for "exception like behaviour") in the example is to define a proper return type:

struct stack{
    double* pData;
    uint32  size;
};

struct popRC{
    double value;
    uint32 size_before_pop;
};

popRC pop(struct stack* pS){
    popRC rc;
    rc.size=pS->size;
    if(rc.size){
        --pS->size;
        rc.value=pS->pData[pS->size];
    }
    return rc;
}

usage of course is:

popRC rc = pop(&stack);
if(rc.size_before_pop!=0){
    ....use rc.value

this happens ALL the time, but in C++ to avoid such ambiguities one usually just returns a

std::pair<something,bool>

where the bool is a success indicator - look at some of:

std::set<...>::insert
std::map<...>::insert

alternatively add a double* to the interface and return a(n UNOVERLOADED!!!!) return code say an enum indicating success.

Of course one did not have to return the size in struct popRC. It could have been

enum{FAIL,SUCCESS};

but since size might serve as a useful hint to the pop'er you might as well use it

BTW, I heartily agree that the struct stack interface should have

int empty(struct stack* pS){
    return (pS->size == 0) ? 1 : 0;
}
pgast
A: 
Norman Ramsey
A: 

Something that nobody has mentioned yet, it's pretty ugly though:

int ok=0;

do
{
   /* Do stuff here */

   /* If there is an error */
   break;

   /* If we got to the end without an error */
   ok=1;

} while(0);

if (ok == 0)
{
   printf("Fail.\n");
}
else
{
   printf("Ok.\n");
}
SlappyTheFish