Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C: passing arguments from variadic function to variadic macro

Tags:

c

macros

variadic

I have a standard logging API built into my C code, a nice simple logF(const char *pFormat, ...) thing which has always, so far, been mapped to vprintf(), i.e.:

void logF(const char *pFormat, ...)
{
    va_list args;
    va_start(args, pFormat);
    vprintf(pFormat, args);
    va_end(args);
}

The C code above this API must work on multiple embedded platforms. I've just reached a platform (Nordic NRF52840) where the underlying logging interface I have to work with is presented as a variadic macro of the form NRF_LOG_INFO(...).

QUESTION: how do I correctly pass fn(const char *pFormat, ...) into a BLAH(...) macro? My brain hurts....

This is with GCC 4.9.3, though it would be nice to have a solution that is relatively relaxed about C compiler version.

EDIT 1: noted that I could redefine my logF() function to be a variadic macro and map it there, the issue is that I would then have a platform-specific header file rather than a generic one, I would have to move it down into the platform code and have one for each. Not impossible but more messy.

EDIT 2: I was asked for the trail of how NRF_LOG_INFO() expands. Here's the relevant output of the pre-processor:

#define NRF_LOG_INFO(...) NRF_LOG_INTERNAL_INFO( __VA_ARGS__)

#define NRF_LOG_INTERNAL_INFO(...) NRF_LOG_INTERNAL_MODULE(NRF_LOG_SEVERITY_INFO, NRF_LOG_SEVERITY_INFO, __VA_ARGS__)

#define NRF_LOG_INTERNAL_MODULE(level,level_id,...) if (NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= level) && (level <= NRF_LOG_DEFAULT_LEVEL)) { if (NRF_LOG_FILTER >= level) { LOG_INTERNAL(LOG_SEVERITY_MOD_ID(level_id), __VA_ARGS__); } }

#define LOG_INTERNAL(type,...) LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( __VA_ARGS__), type, __VA_ARGS__)

#define LOG_INTERNAL_X(N,...) CONCAT_2(LOG_INTERNAL_, N) (__VA_ARGS__)

Then depending on number of args, anything up to:

#define LOG_INTERNAL_6(type,str,arg0,arg1,arg2,arg3,arg4,arg5) nrf_log_frontend_std_6(type, str, (uint32_t)(arg0), (uint32_t)(arg1), (uint32_t)(arg2), (uint32_t)(arg3), (uint32_t)(arg4), (uint32_t)(arg5))

void nrf_log_frontend_std_6(uint32_t severity_mid,
                            char const * const p_str,
                            uint32_t val0,
                            uint32_t val1,
                            uint32_t val2,
                            uint32_t val3,
                            uint32_t val4,
                            uint32_t val5);
like image 542
Rob Avatar asked Mar 25 '20 14:03

Rob


People also ask

How do I use variadic macros?

To use variadic macros, the ellipsis may be specified as the final formal argument in a macro definition, and the replacement identifier __VA_ARGS__ may be used in the definition to insert the extra arguments. __VA_ARGS__ is replaced by all of the arguments that match the ellipsis, including commas between them.

What is __ Va_args __ C++?

macro expansion possible specifying __VA_ARGS__ The '...' in the parameter list represents the variadic data when the macro is invoked and the __VA_ARGS__ in the expansion represents the variadic data in the expansion of the macro. Variadic data is of the form of 1 or more preprocessor tokens separated by commas.

What is __ Va_opt __?

__VA_OPT__ is a new feature of variadic macros in C++20. It lets you optionally insert tokens depending on if a variadic macro is invoked with additional arguments. An example usage is comma elision in a standardized manner.

Is printf variadic function?

The C printf() function is implemented as a variadic function. This noncompliant code example swaps its null-terminated byte string and integer parameters with respect to how they are specified in the format string.


1 Answers

It is not possible to pass the arguments from a variadic function to a variadic macro.

As you want to hide the platform specific macro call from the API header you can process the function arguments with vsnprintf instead of vprintf and call the logging macro with format "%s" and the resulting string buffer.

void logF(const char *pFormat, ...)
{
    va_list args;
    /* Choose a reasonable size or replace with dynamic allocation based on the return value of vsnprintf */
    /* This could also be a static variable or a global variable to avoid allocation of a big buffer on the stack. */
    char buffer[1024];

    va_start(args, pFormat);
    vsnprintf(buffer, sizeof(buffer), pFormat, args);

    NRF_LOG_INFO("%s", buffer);

    va_end(args);
}

Note that you may have to call NRF_LOG_FLUSH before the buffer goes out of scope or gets overwritten. See https://devzone.nordicsemi.com/f/nordic-q-a/22647/nrf_log_info-how-to-print-log-with-string-parameter

like image 100
Bodo Avatar answered Sep 18 '22 13:09

Bodo