Updated Solution
If you don't have errorCode
defined (or don't always want it set), use this instead:
#define CALL_AND_LOG_ERROR(callee) \
CheckCallAndPrintError(callee, #callee, __func__, __LINE__, __FILE__)
int CheckCallAndPrintError( int result, const char* callee,
const char* caller, int line, const char* file)
{
if(result)
{
fprintf(stderr, "Error %d returned by function %s called from function %s on line \
%d of file %s\n", result, callee, caller, line, file);
}
return result;
}
Then you can use CALL_AND_LOG_ERROR
as below except that you explicitly have to assign to a variable if you want to keep the value around:
int errorCode = CALL_AND_LOG_ERROR(SomeFunc(a, 0, NULL));
While this solves the dependency on an external variable, I don't know how often the call will be inlined by the optimizer. I doubt the performance impact will be significant either way. Note that both solutions assume the API return values are int
s. If they're some other scalar type (or different scalar types for different functions), you may have to change how they are passed/output.
Original Solution
Calling a macro which calls your function is definitely the way to go here, but I suggest using the ternary operator instead of a control structure. This allows you to use the macro:
- as a standalone statement (and inspect
errorCode
later, if you wish)
- as the controlling statement of a control structure
- in an expression
- as an operand to a function
Sample code (you can play around with it at http://ideone.com/NG8U16):
#include <stdio.h>
// these macros rely on the fact that errorCode is defined in the the caller
#define FUNC_RETURN_ISNT(func, unexpected) \
((errorCode = func) == unexpected) ? printf("error: call %s shouldn't have returned %d but did (from function %s at line %d of file %s)\n", #func, unexpected, __func__, __LINE__, __FILE__), 0 : 1
#define FUNC_RETURN_IS(func, expected) \
((errorCode = func) != expected) ? printf("error: call %s returned %d instead of %d (from function %s at line %d of file %s)\n", #func, errorCode, expected, __func__, __LINE__, __FILE__), 0 : 1
#define ERROR_CHECK(func) \
(errorCode = func) ? printf("error: call %s returned %d (from function %s at line %d of file %s)\n", #func, errorCode, __func__, __LINE__, __FILE__), errorCode : 0
int func(int a, int b, int c)
{
return a^b^c;
}
int func2(void)
{
return -1;
}
int func3(void)
{
static int i = 3;
return i--;
}
int main(void)
{
int a = 0, b = 0, c = 0;
int errorCode;
int (*funcPoint)(void) = func2;
FUNC_RETURN_ISNT(func(1,1,1), 1);
FUNC_RETURN_IS(func(a,b,c), 1);
ERROR_CHECK(func(a,b,1));
if(ERROR_CHECK(func2()))
{
printf("func2 failed error check\n");
}
else
{
printf("func2 passed error check\n");
}
if(ERROR_CHECK(funcPoint()))
{
printf("funcPoint failed error check\n");
}
else
{
printf("funcPoint passed error check\n");
}
if(ERROR_CHECK(func(0,0,0)))
{
printf("func failed error check\n");
}
else
{
printf("func passed error check\n");
}
while(ERROR_CHECK(func3()))
{
printf("retry...\n");
}
switch(ERROR_CHECK(func(1,2,4)))
{
case 0:
printf("okay\n");
break;
case 1:
printf("non-fatal error 1\n");
break;
case 7:
printf("fatal error 7\n");
return 1;
}
return 0;
}
Example output:
error: call func(1,1,1) shouldn't have returned 1 but did (from function main at line 38 of file prog.c)
error: call func(a,b,c) returned 0 instead of 1 (from function main at line 40 of file prog.c)
error: call func(a,b,1) returned 1 (from function main at line 42 of file prog.c)
error: call func2() returned -1 (from function main at line 44 of file prog.c)
func2 failed error check
error: call funcPoint() returned -1 (from function main at line 53 of file prog.c)
funcPoint failed error check
func passed error check
error: call func3() returned 3 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 2 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 1 (from function main at line 71 of file prog.c)
retry...
error: call func(1,2,4) returned 7 (from function main at line 76 of file prog.c)
fatal error 7
There are still some drawbacks, however:
- It's not exactly pretty. The syntax feels awkward to me and it could run afoul of some coding standards.
- It's probably brittle. I haven't given much thought to it, but I'd bet you could break it by passing something relativley reasonable looking.
- It requires
errorCode
to be already defined (and it has to be an integral type due to the printf
). To be fair, however, so does the original code snippet. The macros could be modified to accept the variable as a parameter (it wouldn't always have to be named errorCode
, but it would still have to be defined outside the macro).
- It prints the exact piece of code used to invoke the function. This may actually be a good thing in some (or even most) cases, however I think it's not ideal in the function pointer scenario (granted, probably uncommon when calling into an API). Also, its harder to print the actual values of the passed parameters. Both these could be worked around to a degree, but only at the cost of making it more awkward and/or less portable.