tags:

views:

143

answers:

7

Given the code below:

typedef struct {int a;} test_t;

arbitrary_t test_dosomething(test_t* test) {
    if (test == NULL) {
        //options:
        //1. print an error and let it crash
          //e.g. fprintf(stderr, "null ref at %s:%u", __FILE__, __LINE__);
        //2. stop the world
          //e.g. exit(1);
        //3. return (i.e. function does nothing)
        //4. attempt to re-init test
    }
    printf("%d", test->a); //do something w/ test
}

I want to get a compiler error if test is ever NULL, but I guess that's not possible in C. Since I need to do null checking at runtime, what option is the most proper way to handle it?

+5  A: 

If you never want the function to be called with a null pointer (i.e., the pointer not being null is a precondition to calling the function), you can use an assertion at the top of the function:

assert(test != NULL);

This can help you when debugging to find places in your code where you are calling the function with a null pointer.

James McNellis
I disagree. if you can recuperate from the error you might want to give an error message and have the user do something, or you might want to log the error. Only do an assert if you cannot recuperate.Assert does this ...If the argument expression of this macro with functional form compares equal to zero (i.e., the expression is false), a message is written to the standard error device and abort is called, terminating the program execution.
Romain Hippeau
@Romain: If it is a precondition of calling the function that the pointer is not null, then calling the function with a null pointer is a logic error, and this is exactly the behavior you want.
James McNellis
@Romain: NULL pointer errors are usually caused by programmer errors, although they can also be caused by corrupted memory. Neither condition is really recoverable.
Dietrich Epp
@James: A function asks the user to enter a value to find sine inverse of. The user enters -2 (out of range). What do you do then? I think printing the error and exiting would be better. The function's return value could be checked to call the function again.
N 1.1
@James McNellis I agree, there are valid cases to exit.
Romain Hippeau
@N 1.1: There is no logic error in that case; that is a runtime error. It is certainly possible to recover from a runtime error.
James McNellis
@N 1.1: The arc sine function returns a nan, I would be horrified if it printed out a message to console. The poster asked about NULL. Passing NULL to a function that can't accept it usually indicates a flaw in the program, not a flaw in the data.
Dietrich Epp
I think assert is a valid case. The question even wants a COMPILER ERROR if the pointer is NULL. So, an assert is probably closer to that than *printing something*, or *trying the user to recover by picking other option*...
Pablo Santa Cruz
+1  A: 

I would use an assert at the beginning of the function:

arbitrary_t test_dosomething(test_t* test) {
    assert(test != NULL);
    printf("%d", test->a); //do something w/ test
}

You won't get a compiler error. But when in development (debugging) your program will halt and point you to that particular assert in the code.

Pablo Santa Cruz
+4  A: 

We really can't categorically answer this question. It depends highly on what the software is going to be used for. If it's providing radiation, you would want it to print the error and exit(1);. If you're writing a business app, you probably want to log an error and just return. It's all about the context.

C. Ross
Even in a business app, aborting execution might be the best option - if things have gone so badly awry, such that things that "can't possibly happen" are happening, then you've got an even chance of producing corrupt or bogus output if you try to continue.
caf
@caf True, but it depends on the app, and what your architecture is.
C. Ross
A: 

It really depends on your use-case. If you have an interactive app, prompt and have the user correct the input. If it is a server process you might want to log. If the error is going to make your program go south then do the assert.

Romain Hippeau
A: 

You can't get a compiler error if test is NULL, it is a runtime value, and it could depend on even user input, the compiler has no way to know its value.

To avoid the function being called with a NULL value, use assert:

assert(test != NULL);

This means: my code is not able to handle NULL as input. And it will abort at runtime.

Your function is supposed to return arbitrary_t and it is not returning anything.

You can also send the error back to the caller, in the return value that you are not using right now, for example returning an arbitrary_t* instead, which would be NULL if it could not be allocated (which will be the case if test_t was NUL). The caller would need to test for that.

duncan
A: 

Apparently (I recently searched the web for "C static assert"), C++0x has a static_assert, and the C people are talking about something like that as well for the next C standard. Of course, this implies that there currently is no standard "static assert" in C.

Some people propose C preprocessor macros which define enums or structs which use a boolean expression and the ?: operator to generate an illegal enum/struct definition if the conditional expression fails. (Above web search listed a number of such macro definitions.)

Personally, as I am always using gcc anyway, I just declare such functions as

foo_t function(moo_t *moo) __attribute__(( nonnull(1) ));

and and thus have the compiler give me a warning (-Wnonnull, included in -Wall) if I accidentally call the function with the parameter set to NULL.

ndim
A: 

I can only echo the above - it depends on your application.

A good rule of thumb is to detect an error as early as possible. This generally makes it easier, quicker and cheaper to find and correct the problem. Ideally, you would have caught this at design time. As you point out, you can't catch it at compile time (unless to switch to a "design by contract" language like Eiffel), so you catch it at run time.

And then ... it depends ...

If it's a desktop app then maybe a huge dialog box and exit is the quickest way to get it fixed, if it causes someone to report the error. If you can, it might also be an idea to send an email to the developer (e.g., if it's an in-house app).

If it's a mission-critical or life-critical, you might just have to restart the app (this is the general approach in embedded systems).

Whatever you decide, try to collect as much information as possible about the problem. For instance, you can roll your won macro to wrap around ASSERT which adds FILE and LINE).

I use the following:

#ifdef TESTING
#define ASSERT_MSG(subsystem, message, condition) if (!(condition)) {printf("Assert failed: \"%s\" at line %d in file \"%s\"\n", message, __LINE__, __FILE__); fflush(stdout); abort();}

/* we can also use this, which prints of the failed condition as its message */
#define ASSERT_CONDITION(subsystem, condition) if (!(condition)) {printf("Assert failed: \%s\" at line %d in file \%s\"\n", #condition, __LINE__, __FILE__); fflush(stdout); abort();}
#else
#define ASSERT_MSG(subsystem, message, condition)  if (!condition) DebugTrace(FATAL, subsystem, __FILE__, __LINE__, "%s", message);
#define ASSERT_CONDITION(subsystem, condition)     if (!(condition)) DebugTrace(FATAL, subsystem, __FILE__, __LINE__, "%s", #condition);
#endif
Mawg