Gnubie
Gnubie

Reputation: 2607

Detecting null parameter in preprocessor macro

I have the following macro function in vanilla C:

#define GLOG(format_string, ...) { \
  const char *file = strrchr(__FILE__, '/'); \
  char format[256] = "%s:%s!%d\t"; \
  strncat(format, format_string, 248); \
  strcat(format, "\n"); \
  printf(format, __FUNCTION__, file ? file : __FILE__, __LINE__, ##__VA_ARGS__); \
}

which lets me print a debug message containing the current function, file and line number, e.g.

GLOG("count=%d", count);

might print

do_count:counter.c!123  count=456
  1. How can I modify the function to print all local variables if caller omits format_string? e.g.

    GLOG();
    

    might print

    do_count:counter.c!123  count=456, message="Hello world", array=[7, 8] structure={ptr=0xACE0FBA5E, coord={x=9, y=0}}
    
  2. If that's not possible, how can I modify it to print just the current function, file and line number? e.g.

    do_count:counter.c!123
    

    As is, this returns an error:

    error: expected expression before ‘,’ token

    as the strncat line is simply

    strncat(format, , 248);
    

Upvotes: 2

Views: 3422

Answers (4)

Gnubie
Gnubie

Reputation: 2607

Based on Blagovest Buyukliev's answer, I've come up with the following solution for part 2:

#define GLOG(fmt, ...) do { const char *fn = strrchr(__FILE__, '/');           \
  printf("%s:%s!%d\t"fmt"\n",__func__,fn?++fn:__FILE__,__LINE__,##__VA_ARGS__);\
 } while(0)

using the preprocessor's string concatenation to simply concatenate a null string if the parameter is omitted.

Additionally, I added the do {...} while(0) to swallow the trailing semicolon so that the following if...else works:

if (...)
 GLOG();
else     
 /* do something else */

(idea from http://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html ).

Upvotes: 0

Blagovest Buyukliev
Blagovest Buyukliev

Reputation: 43548

First, inspecting all the local variables at runtime by the process itself seems impossible because C doesn't have any means for reflection.

Second, you would be much better off if you wrote the logging macro like that:

#include <stdio.h>

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

#define GLOGF(fmt, ...) \
  printf("%s:%s " fmt "\n", __func__, __FILE__ "!" TOSTRING(__LINE__), ##__VA_ARGS__)

int main (void) {
  /* main:test.c!xx count=5 */
  GLOGF("count=%d", 5);
  /* main:test.c!xx */
  GLOGF();
  return 0;
}

It is simpler and doesn't incur any additional runtime overhead since the string is concatenated at compile-time.

Also note that I have used __func__ instead of __FUNCTION__, because the latter is non-standard.

Upvotes: 4

Jason
Jason

Reputation: 32530

As others here have mentioned, C does not have reflection features, and therefore you are not going to be capable of capturing the local variables in a macro call. That being said, if you want something to conditionally happen with a macro depending on if there are or are not any arguments to the macro invocation (i.e., your "non-null" and "null" arguments), then you can do something like the following:

#include <string.h>

#define NULL_IDENT ""
#define IDENT(ident_name) #ident_name
#define MACRO(ident_name) \
    if (strcmp(NULL_IDENT, IDENT(ident_name)) == 0) { \
       /* add code for a null argument passed to the macro */ } \
    else { \
       /* add code for a non-null argument passed to the macro */ } 

Upvotes: 1

Some programmer dude
Some programmer dude

Reputation: 409356

I found this link in this answer. It might help you with the first part of the question.

The second, how to get all local variables, is much harder, if not impossible. The reason is that the code, when compiled, doesn't actually have variables, it just have offsets into a memory area (the stack.) It might be possible that your compiler have internal functions that can be used to inspect the stack, but then you only have possible values not the names of the variables. The only solution I see it to use special pre-processor macros to declare local variables, and then a list of structures to represent them for introspection, which will be a lot of both runtime and memory overhead.

Upvotes: 2

Related Questions