Question

This might sound like an interview question but is actually a practical problem.

I am working with an embedded platform, and have available only the equivalents of those functions:

  • printf()
  • snprintf()

Furthermore, the printf() implementation (and signature) is likely to change in the near future, so calls to it have to reside in a separate module in order to be easy to migrate later.

Given that, can I wrap logging calls in some function or macro? The goal is that my source code calls THAT_MACRO("Number of bunnies: %d", numBunnies); in a thousand places, but calls to the above functions are seen only in a single place.

Compiler: arm-gcc -std=c99

Edit: just to mention, but post 2000 best practices and probably a lot earlier, inline functions are far better than macros for numerous reasons.

Was it helpful?

Solution 2

Since you can use C99, I'd wrap it in a variadic macro:

#define TM_PRINTF(f_, ...) printf((f_), __VA_ARGS__)
#define TM_SNPRINTF(s_, sz_, f_, ...) snprintf((s_), (sz_), (f_), __VA_ARGS__)

since you didn't say that you have vprintf or something like it. If you do have something like it, you could wrap it in a function like Sergey L has provided in his answer.


The above TM_PRINTF does not work with an empty VA_ARGS list. At least in GCC it is possible to write:

#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)

The two ## signs remove the excess comma in front of them them if __VA_ARGS__ is empty.

OTHER TIPS

There are 2 ways to do this:

  1. Variadric macro

    #define my_printf(...) printf(__VA_ARGS__)
    
  2. function that forwards va_args

    #include <stdarg.h>
    #include <stdio.h>
    
    void my_printf(const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
    }
    

There are also vsnprintf, vfprintf and whatever you can think of in stdio.

If you can live with having to wrap the call in two parentheses, you can do it like this:

#define THAT_MACRO(pargs)    printf pargs

Then use it:

THAT_MACRO(("This is a string: %s\n", "foo"));
           ^
           |
          OMG

This works since from the preprocessor's point of view, the entire list of arguments becomes one macro argument, which is substituted with the parenthesis.

This is better than just plain doing

#define THAT_MACRO printf

Since it allows you to define it out:

#define THAT_MACRO(pargs)  /* nothing */

This will "eat up" the macro arguments, they will never be part of the compiled code.

UPDATE Of course in C99 this technique is obsolete, just use a variadic macro and be happy.

#define TM_PRINTF(f_, ...) printf((f_), ##__VA_ARGS__)

The ## token will enable the usage TM_PRINTF("aaa");

#define PRINTF(...) printf(__VA_ARGS__)

This works like this:

It defines the parameterized macro PRINTF to accept (up to) infinite arguments, then preprocesses it from PRINTF(...) to printf(__VA_ARGS__). __VA_ARGS__ is used in parameterized macro definitions to denote the arguments given ('cause you can't name infinite arguments, can you?).

Limited library? Embedded system? Need as much performance as possible? No problem!

As demonstrated in this answer to this question, you can use assembly language to wrap function which do not accept VA_LIST into ones that do, implementing your own vprintf at little cost!

While this will work, and almost certainly result in the performance as well as abstraction you want, I would just recommend you get a more feature filled standard library, perhaps by slicing parts of uClibc. Such a solution is surely to be a more portable and overall more useful answer than using assembly, unless you absolutely need every cycle.

That's why such projects exist, after all.

This is a slightly modified version of @ldav1's excellent answer which prints time before the log:

#define TM_PRINTF(f_, ...)                                                                            \
{                                                                                                 \
    struct tm _tm123_;                                                                            \
    struct timeval _xxtv123_;                                                                     \
    gettimeofday(&_xxtv123_, NULL);                                                               \
    localtime_r(&_xxtv123_.tv_sec, &_tm123_);                                                     \
    printf("%2d:%2d:%2d.%d\t", _tm123_.tm_hour, _tm123_.tm_min, _tm123_.tm_sec, _xxtv123_.tv_usec); \
    printf((f_), ##__VA_ARGS__);                                                                  \
};

Below is an example wrapper for the vsprintf() function, from https://www.cplusplus.com/reference/cstdio/vsprintf/:

#include <stdio.h>
#include <stdarg.h>

void PrintFError ( const char * format, ... )
{
   char buffer[256];
   va_list args;
   va_start (args, format);
   vsprintf (buffer,format, args);
   perror (buffer);
   va_end (args);
}

Following the example above, one can implement wrappers for other desired functions from <stdio.h>.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top