C: passing arguments from variadic function to variadic macro - c

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);

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

Related

Variadic Macro for custom print function with argument expansion in C

I am trying replicate the printf for my application. Instead of using stdout, I have a different set of user specific pointers that basically point to the location one wants to print something. So instead of having:
#define print(fmt, ...) printf(stdout, fmt, __VA_ARGS__)
I want something like this:
#define print(x, fmt, ...) dev_printf(x->pointer_to_a_screen, fmt, __VA_ARGS__)
Here the expectation is that the x is a struct that stores a users current context and information with it. I want to automatically expand x to use the pointer_to_a_screen which basically tells dev_printf where to print the given input. Expectation is the dev_printf needs to behave as any other standard printf function and the user can specify variable arguments.
Is this even possible? I keep getting a compile error for above #define print repeatedly and I cant understand why? I can't expand the first argument?
The trailing comma has to be removed in case no arguments are passed. Ie.
#define print(x, fmt, ...) dev_printf(x->pointer_to_a_screen, fmt, __VA_ARGS__)
print(something, "arg: %d", i);
// expands to dev_printf(something->pointer_to_a_screen, "arg: %d", i);
// all fine
// but:
print(something, "no arg");
// expands to dev_printf(something->pointer_to_a_screen, "not arg", );
// ^^
In your case you can just:
#define print(x, ...) dev_printf(x->pointer_to_a_screen, __VA_ARGS__)
Newer code should use __VA_OPT__:
#define print(x, fmt, ...) dev_printf(x->pointer_to_a_screen, fmt __VA_OPT__(,) __VA_ARGS__)
In days before __VA_OPT__ it was typical to use GNU extension ##__VA_ARGS__to:
#define print(x, fmt, ...) dev_printf(x->pointer_to_a_screen, fmt, ##__VA_ARGS__)
You can also use POSIX fopencookie to create a custom stream that you could manipulate with normal fprintf functions.
Each of standard *printf function have equivalent v*printf function. The best is to provide your own dev_vprintf function that would take a va_list and then provide a simple wrapper:
#ifdef __GNUC__
// ex. on gcc compiler this causes printf-like warnings to happen
__attribute__((__format__(__printf__, 2, 3)))
#endif
return_type print(some_type *x, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
return_type e = dev_vprintf(x->pointer_to_a_screen, fmt, va);
va_end(va);
return e;
}
Such wrapper will check for type mismatches, is easy to maintain and it's easy to check for errors.
I would not use macro for that
int mystrangeprintf(MYTYPE *x, const char *fmt, ...)
{
int length;
char buff[256];
va_list va;
va_start(va, fmt);
length = vsnprintf(buff, sizeof(buff), fmt, va);
va_end(va);
dev_printString(x->pointer_to_a_screen, buff);
return length;
}

Separate different types of arguments from va_list

I am trying to write a macro which gets the information and sends that information to another function by splitting the orginal va_list into string and another va_list spawned from the original one.
Below is my code.
Call to macro
/* Usage */
PRINT_LOG("Format log = %d, %f, %s", 1, 2.7, "Test");
My code below
/* my includes here */
#include <stdarg.h>
void printInfo(int level, const char *debugInfo, ...); /* defined in 3rd party API */
void formatLogs(int level, ...);
#define PRINT_LOG(...) formatLogs(0, __VA_ARGS__)
void formatLogs(int level, ...)
{
va_list args;
va_start(args, level);
/* get the first argument from va_list */
const char *debugString = va_arg(args, const char*);
/* here I want to get the rest of the variable args received from PRINT_LOG*/
va_list restOfArgs = ???????; /* restOfArgs should be 1, 2.7, "Test" */
/* Below I want to send the rest of the arguments */
printInfo(level, debugString, args);
va_end(args);
}
Is it possible to send some part of va_list as va_list to another function?? If so, how can i do it?
Thank you very much in advance.
The simplest thing to do, based on the code in your question, is to redefine the macro like so:
#define PRINT_LOG(s, ...) printInfo(0, s, __VA_ARGS__)
and just skip the intermediate function altogether. Because what you're trying to do can't be done like that.
The , ...) variable argument ellipsis is not a va_list. The variable arguments passed to a function aren't realized as a va_list until va_start is invoked. To pass a va_list as a function argument, the function has to have a va_list in its signature, like this:
int vprintf(const char *format, va_list argList);

How to define a c macro with multiple statement

I am trying to define a macro that has two line/statements, it's like:
#define FLUSH_PRINTF(x) printf(x);fflush(stdout);
but it can't work due to the limit that C macros cannot work with ';'.
Is there any reasonable way to work around it?
P.S.: I know the upper example is weird and I should use something like a normal function. but it's just a simple example that I want to question about how to define a multiple statement Macro.
This is an appropriate time to use the do { ... } while (0) idiom.
This is also an appropriate time to use variadic macro arguments.
#define FLUSH_PRINTF(...) \
do { \
printf(__VA_ARGS__); \
fflush(stdout); \
} while (0)
You could also do this with a wrapper function, but it would be more typing, because of the extra boilerplate involved with using vprintf.
#include <stdarg.h>
#include <stdio.h>
/* optional: */ static inline
void
flush_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
fflush(stdout);
}
Use the multiple expression in macro
#define FLUSH_PRINTF(x) (printf(x), fflush(stdout))

Good Verbosity Macro (C99)

I'm looking to write what I would imagine is a fairly common macro. I want to emulate the repeated "-v" options on many POSIX programs by defining a bunch of macros of the following form:
#define V1(str, ...) if(optv >= 1){printf("%s: "str,prog,__VA_ARGS__);}
int main(int argc, char* argv[])
{
// ... stuff ...
int i = 1;
V1("This contains a variable: %d\n",i);
}
// Output:
// ./program: This contains a variable: 1
where optv counts the number of "-v" options found on the command line and prog contains the program name (neither shown). This works well, but the problem is that I have to use a variable. V1("Output") will generate a compiler error. I could always use V1("Output%s","") but there should be a cleaner solution.
The GNU C preprocessor has a special feature that lets you delete the trailing comma when there are no arguments filling the variadic portion by prepending the token-pasting operator ## to __VA_ARGS__:
#define V1(str, ...) if(optv < 1); else printf("%s: "str,prog, ## __VA_ARGS__)
Alternatively, if you wish to remain fully C99 compliant, you could incorporate the the format string parameter into the ellipsis, but in this instance you'll also need to refactor your code since you want to include the extra prog parameter between the format string and the varargs. Something like this might work:
#define V1(...) if(optv < 1); else myprintf(prog, __VA_ARGS__)
int myprintf(const char *prog, const char *fmt, ...)
{
// Print out the program name, then forward the rest onto printf
printf("%s: ", prog);
va_list ap;
va_start(ap, fmt);
int ret = vprintf(fmt, ap);
va_end(ap);
return ret;
}
Then, V1("Output") expands to myprintf(prog, "Output") without using any non-C99 compiler extensions.
EDIT
Also note that I inverted the if condition in the macro, due to some weird issues that can arise if you invoke the macro inside an if statement without braces—see this FAQ for a detailed explanation.
Why don't you use 2 different macros for each verbosity level; one which prints a message and variable, and one which just prints a message?
You should probably write yourself a small support function so that you can do the job cleanly:
extern void vb_print(const char *format, ...);
#define V1(...) do { if (optv >= 1) vb_print(__VA_ARGS__); } while (0)
I assume that both optv and prog are global variables. These would go into a header (you wouldn't write them out in the programs themselves, would you?).
The function can be:
#include <stdio.h>
#include <stdarg.h>
extern const char *prog;
void vb_print(const char *format, ...)
{
va_list args;
va_start(args, format);
printf("%s:", prog);
vprintf(format, args);
va_end(args);
}
There's no rocket science in there. You can tweak the system to your heart's content, allowing a choice of where the information is written, flushing the output, ensuring there's a newline at the end, etc.
Try this:
#define V1X(str, ...) if(optv >= 1) {printf("%s: "str,prog,__VA_ARGS__);} else
#define V1(...) V1X(__VA_ARGS__,0)
I believe that fixes the problem you described, and the else at the end fixed another problem.

Reuse of va_list

I need to do two (or more) passes over a va_list. I have a buffer of some size, and I want to write a formatted string with sprintf into it. If the formatted string doesn't fit in the allocated space I want to double the allocated space and repeat until it fits.
(As a side-note, i would like be able to calculate the length of the formatted string first and allocate enough space, but the only function that I found that can do that is _snprintf, and it is deprecated in VS2005 ...)
Now, so far there are no problems: i use vsnprintf and call va_start before each invokation.
But I've also created a function that takes a va_list as a parameter, instead of "...". Then I cannot use va_start again! I've read about va_copy, but it is not supported in VS2005.
So, how would you do this?
A previous question about the lack of va_copy in MSVC had some decent enough suggestions, including to implement your own version of va_copy for use in MSVC:
#define va_copy(d,s) ((d) = (s))
You might want to throw that into a 'portability' header protected by an #ifndef va_copy and #ifdef _MSC_VER for use on VC.
The va_copy() is supposed to be part of the C99 specification; as with all variadic parameter support is very platform dependent. The va_list, va_copy(), va_start(), va_end() macros are defined in stdarg.h .
GCC: When attempting to reuse a va_list on GCC, one MUST use va_copy(), as the GCC implementation causes the va_list to be modified, causing the pointer to be positioned after last param after use by a v??printf() function.
SUN: When attempting to reuse a va_list in SunStudio (v11,v12), the va_list variable is untouched and can be reused as many times as desired without needing to va_copy().
MS_Visual C: Not certain, but looks like the 2010 VC++ docs do mention 'va_copy()' and may imply reuse of va_list is reasonable, but should be tested.
Example:
#include <stdio.h>
#include <stdarg.h>
/**
* Version of vsprintf that dynamically resizes the given buffer to avoid overrun.
* Uses va_copy() under GCC compile.
**/
int my_vsprintf(char **buffer, char *msg, va_list args)
{
int bufLen = 0;
va_list dupArgs; // localize args copy
#ifdef __GNUC__
va_copy(dupArgs,args); // Clone arguments for reuse by different call to vsprintf.
#else
dupArgs = args; // Simply ptr copy for GCC compatibility
#endif
// Perform 1st pass to calculate required buffer size. The vsnprintf() funct
// returns the number of chars (excluding \0 term) necessary to produce the output.
// Notice the NULL pointer, and zero length.
bufLen = vsnprintf(NULL,0,msg, dupArgs);
// NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
va_end(dupArgs); // cleanup
#endif
*buffer = realloc(*buffer,bufLen + 1); // resize buffer, with \0 term included.
#ifdef __GNUC__
va_copy(dupArgs,args); // Clone arguments for reused by different call to vsprintf.
#endif
// Perform 2nd pass to populate buffer that is sufficient in size,
// with \0 term size included.
bufLen = vsnprintf(buffer, bufLen+1, msg, dupArgs);
// NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
va_end(dupArgs); // cleanup
#endif
return(bufLen); // return chars written to buffer.
}
/**
* Version of sprintf that dynamically resizes the given buffer to avoid buffer overrun
* by simply calling my_vsprintf() with the va_list of arguments.
*
* USage:
**/
int my_sprintf(char **buffer, char *msg, ...)
{
int bufLen = 0;
va_list myArgs;
va_start(myArgs, msg); // Initialize myArgs to first variadic parameter.
// re-use function that takes va_list of arguments.
bufLen = my_vsprintf(buffer, msg, myArgs );
va_end(myArgs);
}
Late to reply but hopefully someone will find this useful.
I needed to use va_copy but being unavailable in vc2005, I searched and found myself on this page.
I was a little concerned by using the seemingly crude:
va_copy(d,s) ((d) = (s))
So I did a little digging around. I wanted to see how va_copy was implemented in vc2013, so I compiled a test program that uses va_copy and disassembled it:
First, its usage according to msdn:
void va_copy(
va_list dest,
va_list src
); // (ISO C99 and later)
disassembly of va_copy as implemented in msvcr120 (the whole 7 lines!):
PUSH EBP
MOV EBP,ESP
MOV EAX,DWORD PTR SS:[EBP+8] ;get address of dest
MOV ECX,DWORD PTR SS:[EBP+0C] ;get address of src
MOV DWORD PTR DS:[EAX],ECX ;move address of src to dest
POP EBP
RETN
So as you can see, it really is as simple as assigning the value of the src va_list to the dest va_list before parsing.
Since I only use ms compilers, and currently use vc2005 with the possibility of upgrading in the future, here's what I went with:
#if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards
#define va_copy(a,b) (a = b)
#endif
I see of no portable way (and I think that va_copy has been introduced in C99 because there was no portable way to achieve its result in c89). A va_list can be a reference type mock up declared as
typedef struct __va_list va_list[1];
(see gmp for another user of that trick) and that explains a lot of the language constraints around them. BTW, don't forget the va_end if portability is important.
If portability is not important, I'd check stdard.h and see if I can hack something considering the true declaration.

Resources