Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract a function name inside a macro

Tags:

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)          
like image 360
marmistrz Avatar asked Jul 06 '17 07:07

marmistrz


People also ask

Can we call a function from macro?

Just type the word Call then space, then type the name of the macro to be called (run). The example below shows how to call Macro2 from Macro1. It's important to note that the two macros DO NOT run at the same time. Once the Call line is hit, Macro2 will be run completely to the end.

Can I call function in C through macro?

The Concept of C MacrosMacros can even accept arguments and such macros are known as function-like macros. It can be useful if tokens are concatenated into code to simplify some complex declarations. Macros provide text replacement functionality at pre-processing time. The above macro (MAX_SIZE) has a value of 10.

What is the name of function?

A named function has 4 basic parts: the function keyword, the name you give it and use to refer to it, the parameter list, and the body of the function. Named functions can also be assigned to attributes on objects, and then you can call the function on the object: function sayHi() { console.

How do you pass a macro to a function?

The more things you check, the more robust the macro is, and the slower it runs. Passing one argument is as easy as passing two: add another argument to the function definition (see Listing 6). When calling a function with two arguments, separate the arguments with a semicolon; for example, =TestMax(3; -4).


2 Answers

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...

like image 119
Aconcagua Avatar answered Sep 24 '22 04:09

Aconcagua


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.

like image 26
chqrlie Avatar answered Sep 21 '22 04:09

chqrlie