views:

146

answers:

1

For my systems programming class we're doing a lot of programming in C and are required to error check most functions as we are currently learning to program with pthreads.

The reason I say this is not really homework, is that it is far above and beyond what is expected for this class. Simply checking each function individually is more than satisfactory. I just feel this is a time-consuming and messy method and hope for a neater solution.

I was wondering if anyone could show me how to write a function that takes any C function as a parameter, followed by all the required parameters for that function, along with a desired return value (in this case the correct one), and performs the following.

if(function_name(param1, param2, ...) != desired_return_value) {
    fprintf(stderr, "program_name: function_name() failed\n");
    perror("function_name(): ");
}

Is this possible? It's hardly required by our course, but it just irks me that virtually ever function I write has to have 4 lines of code to error check it. It makes it bloody hard to read.

Even some other suggestions would be good. I'm just trying to increase readability, so if this is totally the wrong direction, some correct direction would be much appreciated.

EDIT: This should compile under the gnu99 standard ideally :P

EDIT 2: In response to James McNellis: The errors from our functions do not (I believe in this case), need to be handled. Notification only needs to be supplied. We have covered nothing on handling thread/process related errors (which is this subject in a nutshell).

+10  A: 

Writing generic code in C without using macros isn't the easiest thing to do.

For a (very) basic solution using a variadic macro:

#define CALL_AND_CHECK(f, r, ...)                                \
    do {                                                         \
        if (f(__VA_ARGS__) != r)                                 \
        {                                                        \
            fprintf(stderr, "program_name: " #f "() failed\n");  \
            perror(#f "(): ");                                   \
        }                                                        \
    } while (0)

(See Why are there sometimes meaningless do/while and if/else statements in C and C++ macros? for why the "meaningless" do/while loop is used)

Note that printing out an error message and not actually handling the error is almost certainly a bad idea. Generally, different errors need to be handled in different ways, so generic code like this may not be particularly useful. If you don't want to try and recover from any of these errors, you could exit(), which might be okay for an assignment, though in a real-world program you wouldn't want to do that.

James McNellis
@James McNellis We've had massive spiels on how macros are bad, but no real justification. Under what circumstances would this be a problem? Or what are the problems inherent that I should look up and be aware of before using macros.
Ben
@Ben: Macros are bad when they make code harder to understand. They are good when they reduce the amount of code that you have to write and when they make code easier to understand. It's often quite subjective whether a given use is a good idea or not.
James McNellis
Cheers. Gonna leave this open for a while and see if some other answers come in.
Ben
@Ben: this macro is fairly well-behaved: it evaluates each argument exactly once, thus avoiding one surprising thing that macros can do. You must either avoid using it like this: `if (condition) CALL_AND_CHECK(f,r,a,b,c) else CALL_AND_CHECK(f,r,x,y,z);`, which doesn't work, or add the usual macro do/while(0) trick. The inherent problem with macros is that they look like function calls but they're not, they're just text replacement, so they don't interact with the syntax of C in a very intuitive way.
Steve Jessop
@Steve Jessop So as long as the code in the macro can be directly substituted into the place within my program where the macro is called, this should behave correctly?
Ben
@Ben: yes, it's just a question of writing your macros to minimise the chance that in future, you'll forget the restrictions on how they're supposed to be used. If you ever write if/else statements without curly brackets, it's easy to accidentally misuse macros that can attach themselves to an `else`.
Steve Jessop
@James McNellis Thanks for the quick response. Seems to be the way to do it :)
Ben
The macro above really should have the `do..while(0)` to be called well-behaved.
bstpierre
@bstpierre: Why? Isn't the purpose of the do..while(0) hack to ensure that the macro has its own scope "body", which it already does due to the if(..){..}?
Arafangion
@Arafangion - no, it's to ensure that it works correctly with a semicolon (and, eg. doesn't "claim" am else statement or suchlike).
detly
@detly This should not be able to claim an else statement unless the code is written without braces (which I avoid). I am under the impression this is bad practice to write such code. If the code has braces the else will be assigned correctly to the preceding if, not the one in the macro.
Ben
@Ben - sure, but if you use the `do ... while(0)` form, you (or anyone else) will never have to worry about arbitrarily restricting valid C syntax anyway. But more to the point, sometimes you think "oh, I'll just put in a one line `if/else` for now..." and then spend two days debugging some seemingly bizarre problem. (At any rate, I really just wanted to correct the assertion above for the benefit of anyone else reading it.)
detly
@detly Fair call. I am guilty of using bad coding practice for temporary changes. Steve has pointed out the restrictions for usage and I'll probably use the `do ... while(0)` form when implementing this with the intention of saving possible future headaches just in case. Thanks.
Ben
I think Steve makes the most important point, though: the critical word in "function-like macro" is "like." Invoking a function-like macro doesn't result in a function call, it results in a token substitution.
James McNellis
To be honest, with all the talk there is about how useful the `do...while(0)` idiom is, I have never had a bug caused by not using it. Granted, some people here have been writing code in C since before I was born, so I'd tend to trust their advice based on experience more than I'd trust mine...
James McNellis
@James McNellis - it's more a problem when code is written by different people, at least one of whom assumes that everyone else uses the `do ... while(0)` trick...
detly
I think "arbitrarily restricting" usage is a good observation. Given the behaviour of the macro, it could be implemented `if (foo) { bar }`, or `if (foo) bar`, or `foo ? bar : (void)0`, or `(foo ? bar : (void)0)`, or `do { if (foo) bar; } while(0)`. (There are two statements in this case, but `bar` could be both of them, comma-separated, or a function call). Some forms impose more annoying restrictions than others. Rather than make people wonder what they can and can't do with your macro, the intent of the do/while(0) trick is to be user-friendly by imposing light, "natural", restrictions.
Steve Jessop
I don't think I've ever had a serious, time-consuming bug caused by a macro grabbing an `else`, but I think I have misused other people's macros before in this way. I'm certain that I've "misused" macros which evaluate to expressions, where those expressions don't have surrounding parens and can result in screwy operator precedence. In those cases it's a lot more clear-cut that the macro itself is unfriendly/buggy, rather than my usage being optimistic. I consider both to be issues of hygiene, though.
Steve Jessop
Oh, and for the record, I think I might just barely have been writing code since before James was born, but certainly not C code, so you don't have to accept my advice on those grounds :-)
Steve Jessop
There is no good reason other than laziness not to use the do...while(0), so I've added it as I was just reminded that one shouldn't be lazy, even in example code. That said, it would probably also be useful to use an indirect stringize macro in the event that `f` is actually itself a macro name; it depends on how the macro is intended to be used and what is best for a user of the program to see.
James McNellis