15

I come from a C# background, but I'm learning C at the moment. 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 user pushed a 0.0 earlier on the stack, how do you know whether the stack is empty or whether a correct value was returned?

Malcolm
  • 41,014
  • 11
  • 68
  • 91
Andreas Grech
  • 105,982
  • 98
  • 297
  • 360

12 Answers12

16

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
  • 74,820
  • 18
  • 121
  • 166
  • 2
    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 Aug 02 '09 at 18:28
  • 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 Aug 02 '09 at 18:30
  • ANSI has codified `setjmp' so it is guaranteed to work (at least if the compiler conforms to the standard), but stack switch and concurrency have not been standardized, so it is still a problem to write a safe threads package for C (even using asm to do the context switch) because a compiler *could* (though it might be unlikely) perform optimizations and transforms that break assumptions in the threads package. – Jonke Aug 02 '09 at 18:35
  • 2
    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 Aug 02 '09 at 18:46
  • 6
    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 Aug 02 '09 at 18:46
  • well actually since I knew there are no exceptions in C#, I asked for the suggested way to go about signalling errors – Andreas Grech Aug 02 '09 at 19:13
  • The way to signal errors in C is nearly always through return codes, as your copy of K&R teaches. The special case in your example was that there were no values available to use as error codes. – Tyler McHenry Aug 02 '09 at 19:48
  • 1
    @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 S. Aug 02 '09 at 21:27
  • 1
    no no sorry, i meant C. that "C#" in my previous comment was a typo – Andreas Grech Aug 02 '09 at 21:54
  • 1
    @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 Aug 02 '09 at 21:55
11

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".

alinsoar
  • 15,386
  • 4
  • 57
  • 74
Ville Laurikari
  • 28,380
  • 7
  • 60
  • 55
  • 4
    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. –  Aug 02 '09 at 18:49
  • @Neil: I liked the second part of that sentence :-) – jeroenh Aug 02 '09 at 21:40
  • 1
    "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 Aug 02 '09 at 23:13
  • I guess that THROW statement won't work when called from a deeper function. Within a function one can simply use goto. – Calmarius Mar 11 '18 at 14:46
7

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
  • 273,490
  • 39
  • 460
  • 699
4

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 :-)

  • More usefully for this particular case, C++ has a stack right there in the library :-) – Steve Jessop Aug 02 '09 at 23:17
  • But the C++ std::stack pop function does not actually do what the OP wants. –  Aug 02 '09 at 23:34
  • 1
    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 Aug 02 '09 at 23:46
4

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;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
pgast
  • 1,657
  • 10
  • 11
3

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
  • 223,662
  • 58
  • 417
  • 506
2

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
  • 5,020
  • 2
  • 32
  • 35
1

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
  • 19,391
  • 1
  • 51
  • 53
1

setjmp, longjmp, and macros. It's been done any number of times—the oldest implementation I know of is by Eric Roberts and Mark vanderVoorde—but the one I use currently is part of Dave Hanson's C Interfaces and Implementations and is free from Princeton.

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
0

you can return a pointer to double:

  • non-NULL -> valid
  • NULL -> invalid
dfa
  • 114,442
  • 31
  • 189
  • 228
  • downvotes without comments are pointless, please explain your downvotes – dfa Aug 02 '09 at 18:48
  • 3
    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 Aug 02 '09 at 18:56
  • 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 Aug 03 '09 at 15:34
0

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
  • 4,755
  • 7
  • 47
  • 64
0

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
  • 2,344
  • 3
  • 34
  • 41