Bruce
Bruce

Reputation: 35295

When should we use asserts in C?

I am writing a function in C. As a matter of style, when is it good to use assert compared to returning an error code. Lets say the function is dividing two numbers. Should I assert the divisor be non-zero or should I return an error code? Please give more examples, if you can, to make the distinction clear.

Upvotes: 49

Views: 46398

Answers (10)

Steve Summit
Steve Summit

Reputation: 48133

Here's a real-world example of an assertion I wrote yesterday.

I had two parallel arrays — let's call them a and b — and I was about to run an index i from 0 up to the size of a, and then do something with a[i] and b[i]. But if there were fewer elements in b than in a, my code would have an array bounds violation on b. But other parts of the code are supposed to keep the sizes of a and b identical. So I put an assertion before the loop, asserting that the sizes of a and b were equal.

The sizes should be equal — but if somehow they're not, I want the code to fail on the assertion that tells me why, rather than failing strangely on the Undefined Behavior that would result when I tried to read beyond the end of array b, if it were smaller.

Upvotes: 3

Dean P
Dean P

Reputation: 2225

The essence of assert is to catch errors that the programmer believes should never happen, but may happen

Let us see it in action and why it helps us.

We have designed a function with a control statement to test whether our animal is a dog or a cat. Our code only deals in dogs and cats, and as a programmer, we believe the else statement will never execute. But it may execute despite our designs due to an unknown error (accidental initialisations and assignments, undetected overflows, etc).

void isDogOrCat(T_animal x){
   if(isDog(x)){
       printf("Woof\n");
   }
   else if(isCat(x)){
       printf("Meow\n");
   }
   else{
       assert(false);
   }
}

If an error happens to materialise, we are immediately pointed to the assertion that failed and our consequent debug attempt is a breeze. Compare this with just having:

   if(isDog(x)){
       printf("Woof\n");
   }
   else{
       printf("Meow\n");
   }

If a bug slips in here, you are in much bigger trouble. You will think you are passing in a cat on account of the output, but unbeknownst to you, you are dealing with a variable that is erroneously outside your designed bounds!

(One could add in an else if(isCat(x)) instead of the else but in that case, neither statement would execute and the functions wouldnt output anything - again, against our design)

Upvotes: 1

Kerrek SB
Kerrek SB

Reputation: 477710

An error code signals runtime behaviour. An assertion is a debugging tool that allows the developer to assert that their assumptions about the program logic are indeed true.

They're two completely different things with different applications.

Error codes are part of your normal program flow. Assertions are only for debugging, and if an assertion is triggered, that means that your program is not written correctly.

Upvotes: 20

Fred Foo
Fred Foo

Reputation: 363858

assert aborts the process, but is turned into a no-op when the program is compiled with -DNDEBUG, so it's a rather crude debugging tool and nothing more than that. You should only use assert to check for situations that "can't happen", e.g. that violate the invariants or postconditions of an algorithm, but probably not for input validation (certainly not in libraries). When detecting invalid input from clients, be friendly and return an error code.

An example use of assert could be: you've implemented an incredibly smart sorting algorithm and you want to check whether it really sorts. Since the sorting function is supposed to "just work" and therefore doesn't return a value, you can't add error returns without changing the API.

void sort(int *a, size_t n)
{
    recursive_super_duper_sort(a, 0, n);
    assert(is_sorted(a, n));
}

static bool is_sorted(int const *a, size_t n)
{
    for (size_t i=0; i<n-1; i++)
        if (a[i] > a[i+1])
            return false;

    return true;
}

In the long run, you'd really want a proper unit testing framework for this kind of thing instead of assert, but it's useful as a temporary debugging tool.

Upvotes: 62

david van brink
david van brink

Reputation: 3652

The conventional wisdom is to use assert() to help debug your code, to warn you when something "impossible", something that must not happen, has happened. This "warning" takes the form of exiting your program.

I've heard Jim Coplien (general C++ guru and SCRUM trainer) advocate leaving your asserts active in deployed code. (It sounds crazy, I know...) This was specifically for high-reliability server code. The motivation was that it's better to fail, hard, and let another node take over, than to tolerate your server being in an "impossible" state.

(And of course, track the failures, and analyze them. It means there is a bug or incorrect assumption.)

Upvotes: 11

Michael Thuma
Michael Thuma

Reputation: 31

Use an assert when your program meets a situation that does not allow to continue. Assertions are a 'contract' and I use them as a 'contract' with OS and situation 'danger in delay'.

In order to emulate exceptions you can still use GOTO 'ERRORLABEL' and terminate clean after running a clean up function.

Upvotes: 3

First, assert from the <assert.h> header can be disabled (e.g. by compiling with gcc -DNDEBUG), and sometimes is disabled for the "production" version of a binary.

Second, as stated by Linux man page,

   The  purpose  of  this macro is to help the programmer find bugs in his
   program.   The  message  "assertion  failed  in  file  foo.c,  function
   do_bar(), line 1287" is of no help at all to a user.

So assert should fail only in buggy situations. In exceptional or error situations, you should do something else.

Some tools (or even compilers) might use assert-ions to e.g. optimize your code.

In your example of a quotient function, you'll use assert if, inside your entire program, you are sure that the divisor should be non-zero (but then it could make sense to name the function differently, perhaps quotient_by_non_zero). If you consider that it could happen, make it a fatal message, an exception (i.e. longjmp in C), an error code, etc.

Upvotes: 6

ibid
ibid

Reputation: 3902

This is really a matter of taste. Here is my opinion.

The main rule of thumb: an assertion failure is always a bug in the program.

Use an assert to check function parameters if you expect the caller to ensure that the argument is correct and you want to indicate that any other behavior is a bug in the caller. Dividing by zero is, IMO, a very good example.

Use an error code if you expect the caller not to be able to ensure that the argument is correct before calling. For example, it could be very computationally expensive to check the arguments beforehand.

Never use an assert to check user input.

Upvotes: 10

bobbymcr
bobbymcr

Reputation: 24177

In general, asserts are for the programmer (i.e. you) to find logic/programming errors before releasing the program to real users. Asserts should not be used for detecting runtime input errors -- use error codes for these.

Upvotes: 23

David Heffernan
David Heffernan

Reputation: 613612

Since C does not support exceptions you don't have any real option other than to return an error code. A failing C assert() results in abort() being called which bombs the process. That's not really comparable with standard error handling.

For floating point operations you can use NaN to signal error conditions. For integer operations an error code is simply your only option.

Upvotes: 4

Related Questions