marmistrz
marmistrz

Reputation: 6414

Extract a function name inside a macro

In C, we often have to run such code

if (! somefun(x, y, z)) {
    perror("somefun")
}

Is it possible to create a macro which, used as follows:

#define chkerr ...
chkerr(somefun(x, y, z));

would compile to the above?

I already know I can use __VA_ARGS__ macro, but this would require me to call it like

chkerr(somefun, x, y, z)         

Upvotes: 24

Views: 5677

Answers (5)

Aconcagua
Aconcagua

Reputation: 25536

Short variant (you spotted already):

#define chkErr(FUNCTION, ...)  \
    if(!FUNCTION(__VA_ARGS__)) \
    {                          \
        perror(#FUNCTION);     \
    }

Be aware that this can impose big problems in nested if/else or similar constructs:

if(x)
    chkErr(f, 10, 12) //;
                      //^ semicolon forgotten!
else
    chkErr(f, 12, 10);

would compile to code equivalent to the following:

if(x)
{
    if(!f(10, 12))
        perror("f");
    else if(!f, 12, 10))
        perror("f");
}

Quite obviously not what was intended with the if/else written with the macros... So you really should prefer to let it look like a real function (requiring a semicolon):

#define chkErr(FUNCTION, ...)      \
    do                             \
    {                              \
        if(!FUNCTION(__VA_ARGS__)) \
        {                          \
            perror(#FUNCTION);     \
        }                          \
    }                              \
    while(0)

You would call it like this:

chkErr(someFunction, 10, 12);

In case of error, output would be:

someFunction: <error text>

However, this hides the fact that a function actually gets called, making it more difficult to understand for "outsiders". Same output, not hiding the function call, but requiring one additional comma in between function and arguments (compared to a normal function call):

#define chkErr(FUNCTION, ARGUMENTS) \
do                                  \
{                                   \
    if(!FUNCTION ARGUMENTS)         \
    {                               \
        perror(#FUNCTION);          \
    }                               \
}                                   \
while(0)

chkErr(someFunction,(12, 10));
//                 ^ (!)

Another variant with the charm of retaining the function call would print out this entire function call:

#define chkErr(FUNCTION_CALL)   \
do                              \
{                               \
    if(!FUNCTION_CALL)          \
    {                           \
        perror(#FUNCTION_CALL); \
    }                           \
}                               \
while(0)

chkErr(someFunction(10, 12));

In case of error, output would be:

someFunction(10, 12): <error text>

Addendum: If you really want exactly the output as shown in the question and still have the function call retained (without comma in between), you are a little in trouble. Actually, it is possible, but it requires some extra work:

Problem is how the preprocessor operates on macro arguments: Each argument is a token. It can easily combine tokens, but cannot split them.

Leaving out any commas results in the macro accepting one single token, just as in my second variant. Sure, you can stringify it, as I did, but you get the function arguments with. This is a string literal, and as the pre-processor cannot modify string literals, you have to operate on them at runtime.

Next problem then is, though, string literals are unmodifiable. So you need to modify a copy!

The following variant would do all this work for you:

#define chkErr(FUNCTION_CALL)                                 \
do                                                            \
{                                                             \
    if(!FUNCTION_CALL)                                        \
    {                                                         \
        char function_name[] = #FUNCTION_CALL;                \
        char* function_name_end = strchr(function_name, '('); \
        if(function_name_end)                                 \
            *function_name_end = 0;                           \
        perror(function_name);                                \
    }                                                         \
}                                                             \
while(0)

Well, decide you if it is worth the effort...

By the way - whitespace between function name and opening parenthesis is not eliminated. If you want to be perfect:

unsigned char* end = (unsigned char*) function_name;
while(*end && *end != '(' && !isspace(*end))
    ++end;
*end = 0;

Or, much nicer (thanks chqrlie for the hint):

function_name[strcspn(function_name, "( \t")] = 0;

Anything else I can think of would require an additional pre-processing step:

#define CAT(X, Y) CAT_(X, Y)
#define CAT_(X, Y) X ## Y

#define chkErr(FUNCTION_CALL)                 \
do                                            \
{                                             \
    if(!FUNCTION_CALL)                        \
    {                                         \
        perror(CAT(CHK_ERR_TEXT_, __LINE__)); \
    }                                         \
}                                             \
while 0

chkErr(function(10, 12));

Ah, huh, this would result in code like this:

if(!function(10, 12))
{
    perror(CHK_ERR_TEXT_42);
}

And now, where to get these macros from? Well, the pre-processing, remember? Possibly a perl or python script, e. g. generating an additional header file you'd have to include. You would have to make sure this pre-processing is done every time before the compiler's pre-processor runs.

Well, all not impossible to solve, but I'll leave this to the masochists among us...

Upvotes: 21

chqrlie
chqrlie

Reputation: 145287

C11 6.4.2.2 Predefined identifiers

The identifier __func__ shall be implicitly declared by the translator as if, immediately following the opening brace of each function definition, the declaration

static const char __func__[] = "function-name";

appeared, where function-name is the name of the lexically-enclosing function.

You can used it this way:

#define chkErr(exp)  do { if (!(exp)) perror(__func__); } while (0)

chkerr(somefun(x, y, z));

Unfortunately, this would produce an error message with the name of the calling function, not somefun. Here is a simple variant that should work and even produce more informative error messages:

#define chkErr(exp)  do { if (!(exp)) perror(#exp); } while (0)

chkerr(somefun(x, y, z));

In case somefun(x, y, z) returns a non zero value, the error message will contain the string "somefun(x, y, z)".

You can combine both techniques to give both the offending call and the location:

#include <errno.h>
#include <stdio.h>
#include <string.h>

#define chkErr(exp)  \
    do { if (!(exp)) \
        fprintf(stderr, "%s:%d: in function %s, %s failed: %s\n",\
                __FILE__, __LINE__, __func__, #exp, strerror(errno)); \
    } while (0)

chkerr(somefun(x, y, z));

This assumes somefun() returns 0 or NULL in case of error and set errno accordingly. Note however that most system calls return non zero in case of error.

Upvotes: 8

Lundin
Lundin

Reputation: 214810

Yes it is possible with an ugly, unsafe variadic macro:

#define chkerr(func, ...) \
if(!func(__VA_ARGS__))    \
{                         \
  perror(#func);          \
}

...
chkerr(somefunc, 1, 2, 3);

But it is a very bad idea.


Call for sanity:

If there was just the original code with the plain if statement, the reader would think "Here they call a function and do some basic error control. Okay, basic stuff. Moving on...". But after the changes, anyone who reads the code will instead freeze and think "WTF is this???".

You can never write a macro that is clearer than the if statement - which makes the if statement superior to the macro.

Some rules to follow:

  • Function-like macros are dangerous and unreadable. They should only be used as the very last resort.
  • Avoid inventing your own secret macro language with function-like macros. C programmers who read your code know C. They don't know your secret macro language.
  • "To avoid typing" is often a poor rationale for program design decisions. Avoiding code repetition is a good rationale, but taking it to the extremes will affect code readability. If you avoid code repetition and make the code more readable at the same time, it is a good thing. If you do it but the code turns less readable, it is hard to justify.

Upvotes: 2

ugoren
ugoren

Reputation: 16451

You can use the original call format:

chkerr(somefun(x, y, z));

With a macro and a helper function:

#define chkerr(fcall) \
    if (!fcall) { \
        perror(extract_fname(#fcall)); \
    }
const char *extract_fname(const char *fcall);

The extract_fname function would get text and return everything until the open parenthesis.

Upvotes: 2

user1350184
user1350184

Reputation:

It's not possible to extract just the function name. The C processor sees the literals you pass as single tokens, which can't be manipulated. Your only options are to print the function with arguments like Aconcague suggests or pass the name as a separate parameter:

#define chkErr(FUNCTION_NAME, FUNCTION_CALL) \
if(!FUNCTION_CALL) \
{ \
    perror(#FUNCTION_NAME); \
}

chkErr(someFunction, someFunction(10, 12));

Upvotes: 1

Related Questions