Va_list misbehavior on Linux

I have code that converts variable parameters to va_list and then passes the list to a function that then calls vsnprintf . This works fine on Windows and OS X, but on Linux it doesn't work with odd results.

In the following code example:

 #include <string.h> #include <stdio.h> #include <stdarg.h> #include <stdlib.h> char *myPrintfInner(const char *message, va_list params) { va_list *original = &params; size_t length = vsnprintf(NULL, 0, message, *original); char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, params); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); return final; } char *myPrintf(const char *message, ...) { va_list va_args; va_start(va_args, message); size_t length = vsnprintf(NULL, 0, message, va_args); char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, va_args); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); va_end(va_args); return final; } int main(int argc, char **argv) { char *test = myPrintf("This is a %s.", "test"); char *actual = "This is a test."; int result = strcmp(test, actual); if (result != 0) { printf("%d: Test failure!\r\n", result); } else { printf("Test succeeded.\r\n"); } return 0; } 

The output of the second vsnprintf call is 17, and the result of strcmp is 31; but I donโ€™t understand why vsnprintf will return 17 after seeing This is a test. is 15 characters, add NULL and you get 16.

Related topics that I saw but not addressing the topic:

  • Skip va_list or pointer to va_list?
  • Passing one va_list as a parameter to another

With @Mat's answer (I am reusing the va_list object, which is invalid), this happens right around the first associated stream that I am associated with. So I tried using this code:

 char *myPrintfInner(const char *message, va_list params) { va_list *original = &params; size_t length = vsnprintf(NULL, 0, message, params); char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, *original); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); return final; } 

What, for the C99 specification (footnote in section 7.15), should work:

It is allowed to create a pointer to va_list and pass this pointer to another function, in which case the original function can continue to use the original list after returning another function.

But my compiler (gcc 4.4.5 in C99 mode) gives me this error regarding the first line of myPrintfInner :

 test.c: In function 'myPrintfInner': test.c:8: warning: initialization from incompatible pointer type 

And the resulting binary produces the same effect as the first time.


Found: Is the GCC pointer to va_list passed to the function incorrectly?

The proposed workaround (which is not guaranteed to work, but in practice) is to use arg_copy first:

 char *myPrintfInner(const char *message, va_list params) { va_list args_copy; va_copy(args_copy, params); size_t length = vsnprintf(NULL, 0, message, params); char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, args_copy); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); return final; } 
+8
c variadic-functions
source share
2 answers

As Mat notes, the problem is that you are reusing va_list . If you donโ€™t want to restructure your code as it suggests, you can use the C99 va_copy() macro, for example:

 char *myPrintfInner(const char *message, va_list params) { va_list copy; va_copy(copy, params); size_t length = vsnprintf(NULL, 0, message, copy); va_end(copy); char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, params); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); return final; } 

On compilers that do not support C99, you can use __va_copy() or define your own implementation of va_copy() (which will not be portable, but you can always use the compiler / platform flash in the header file if you really need to). But in fact, it was 13 years old; any decent compiler should support C99 these days, at least if you give it the right options ( -std=c99 for GCC).

+11
source share

The problem is that (in addition to the missing return statement) you reuse the va_list parameter without reloading it. This is not good.

Try something like:

 size_t myPrintfInnerLen(const char *message, va_list params) { return vsnprintf(NULL, 0, message, params); } char *myPrintfInner(size_t length, const char *message, va_list params) { char *final = (char *) malloc((length + 1) * sizeof(char)); int result = vsnprintf(final, length + 1, message, params); printf("vsnprintf result: %d\r\n", result); printf("%s\r\n", final); return final; } char *myPrintf(const char *message, ...) { va_list va_args; va_start(va_args, message); size_t length = myPrintfInnerLen(message, va_args); va_end(va_args); va_start(va_args, message); char *ret = myPrintfInner(length, message, va_args); va_end(va_args); return ret; } 

(And turn on compiler warnings.)

I do not think that the footnote you are referring to means what you think. I read it as: if you pass va_list directly (as a value, not a pointer), the only thing you can do in the caller is va_end it. But if you pass it as a pointer, you could, say, call va_arg in the caller if the caller did not "consume" all va_list .

You can try va_copy . Something like:

 char *myPrintfInner(const char *message, va_list params) { va_list temp; va_copy(temp, params); size_t length = vsnprintf(NULL, 0, message, temp); ... 
+7
source share

All Articles