I have some code that converts variadic parameters into a va_list, then passes the list on to a function that then calls vsnprintf. This works fine on Windows and OS X, but it is failing with odd results on Linux.
In the following code sample:
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = ¶ms;
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 second vsnprintf call is 17, and the result of strcmp is 31; but I don't get why vsnprintf would return 17 seeing as This is a test. is 15 characters, add the NULL and you get 16.
Related threads that I've seen but do not address the topic:
Pass 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 not allowed), this comes squarely around to the first related thread I linked to. So I attempted this code instead:
char *myPrintfInner(const char *message, va_list params)
{
va_list *original = ¶ms;
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;
}
Which, per the C99 spec (footnote in Section 7.15), should work:
It is permitted to create a pointer to a va_list and pass that pointer
to another function, in which case the original function may make
further use of the original list after the other function returns.
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 exact same effect as the first time around.
Found this: Is GCC mishandling a pointer to a va_list passed to a function?
The suggested workaround (which wasn't guaranteed to work, but did 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;
}
As Mat notes, the problem is that you're reusing the va_list. If you don't want to restructure your code as he suggests, you can use the C99 va_copy() macro, like this:
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 don't support C99, you may be able use __va_copy() instead or define your own va_copy() implementation (which will be non-portable, but you can always use compiler / platform sniffing in a header file if you really need to). But really, it's been 13 years — any decent compiler should support C99 these days, at least if you give it the right options (-std=c99 for GCC).
The problem is that (apart from the missing return statement) you're re-using the va_list parameter without resetting it. That's 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 your compiler's warnings.)
I don't think the footnote you point to means what you think it does. I read it as: if you pass a 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 callee didn't "consume" all the va_list.
You could try with va_copy though. 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);
...
Related
I have a function A with a body like so
char* A (const char* arg) {
char ret[8192];
B(ret);
return strdup(ret);
}
Function B looks like this (some pseudo code on iteration logic for brevity)
void B(char* ret) {
char retString[8192];
while(ITERATIONS_LEFT) {
snprintf(returnString, 8192, "\n Format %s\n\n", IT_VALUE);
snprintf(returnString, 8192, "\n Val %s\n\n", IT_VALUE_2);
}
strcpy(ret, returnString);
}
So essentially I have a function A that gives another function B a string buffer for B to enter formatted data into. Now this works fine as long as the total data returned from the iterations does not exceed 8196 (just a guess at a 'sufficiently large' value) but I think it would be better if I could do this dynamically and not have to worry about the case where my buffer fills. How would I achieve this in a fairly efficient manner, with the constraints that function A must still call function B, and that B's signature can be changed but A's cannot?
In addition to your allocation problem, you overwrite the same string here:
snprintf(returnString, 8192, "\n Format %s\n\n", IT_VALUE);
snprintf(returnString, 8192, "\n Val %s\n\n", IT_VALUE_2);
You could solve this with a kind of "appender" that re-allocates memory as it is needed by determining the required length by passing a size of 0 to snprintf as Joachim Pileborg suggested:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
struct append_t {
char *str; /* string */
size_t len; /* length of string */
size_t size; /* allocated size */
};
void append(struct append_t *app, const char *fmt, ...)
{
va_list arg;
size_t len;
va_start(arg, fmt);
len = vsnprintf(NULL, 0, fmt, arg);
va_end(arg);
while (app->len + len + 1 >= app->size) {
app->size = app->size ? app->size * 2 : 0x100;
app->str = realloc(app->str, app->size);
// Check and handle error
}
va_start(arg, fmt);
len = vsnprintf(app->str + app->len, app->size - app->len, fmt, arg);
va_end(arg);
app->len += len;
}
int main(int argc, char **argv)
{
struct append_t app = {NULL};
for (int i = 1; i < argc; i++) {
if (i > 1) append(&app, ", ");
append(&app, "'%s'", argv[i]);
}
if (app.str) puts(app.str);
free(app.str);
return 0;
}
Things to note:
The code uses the fact that realloc(NULL, size) behaves like malloc(size). The appender must be initialised to all zero.
vsnprintf is a variant of snprintf that takes a va_list instead of variadic arguments. The v...printf functions allow you to write your own printf-like functions. You can't pass variadic arguments to other functions, you have to create a va_list with the va_... macros from the <stdarg.h> header.
Most compilers can detect mismatches between printing formats and arguments for the standard printf functions. If you wat to benefit from these checks for your function, you could use the appropriate GCC attributes ((format(printf, 2, 3)) or the SAL annotation _Printf_format_string_.
In your example, A would create the appender and pass it to B and then return its .str. You could also return an appender from B and return its .strfrom A.
I suggest the following versions of A and B.
char* A (const char* arg) {
int size = 8192;
char *ret = malloc(size);
B(ret, size);
return ret;
}
void B(char* ret, int size) {
int pos = 0, required;
while(ITERATIONS_LEFT) {
required = snprintf(NULL, 0, "\n Format %s\n\n", IT_VALUE);
if (pos + required >= size) {
size *= 2;
ret = realloc(ret, size);
}
pos += sprintf(ret + pos, "\n Format %s\n\n", IT_VALUE);
required = snprintf(NULL, 0, "\n Val %s\n\n", IT_VALUE_2);
if (pos + required >= size) {
size *= 2;
ret = realloc(ret, size);
}
pos += sprintf(ret + pos, "\n Val %s\n\n", IT_VALUE_2);
}
}
Note that:
buffer size is doubled if it isn't enough. That works well in most cases.
copying is minimized (no strdup or strcpy)
you may want to make a new function with the repeated code in the while loop
In your version of B buffer is overwritten each time you call snprintf. Here the writing position (pos) is updated to append (null terminating char overwritten)
snprintf with NULL argument will return the required buffer size without printing anything anywhere
You may want to check that ret is not NULL after calling realloc
If ret is not NULL it's certain that the buffer is large enough. Thus simple sprintf is used to actually print.
Remember to free the buffer!
I haven't tested the code myself
Is there a va_list equivalent of snprintf which takes a va_list of variable arguments? I'm trying to implement two functions:
char * __HYP format_cstring(const char * format, ...);
chat * __HYP format_cstringv(const char * format, var_list args);
But I'm not sure how to apply snprintf to this situation. Something like this (notice the question marks):
char * __HYP format_cstring(const char * format, ...)
{
int size = snprintf(NULL, 0, format, ??);
char * buffer = (char *)malloc(size * sizeof(char));
if (snprintf(buffer, size, format, ??) < 0) {
free(buffer);
return NULL;
}
return buffer;
}
And what about its format_cstringv counterpart?
Here's how I ended up doing it:
// .h
char * sformat(const char * format, ...) __attribute__((format (printf, 1, 2)));
char * vsformat(const char * format, va_list args) __attribute__((format (printf, 1, 0)));
And the implementation:
char * __HYP sformat(const char * format, ...)
{
char * buffer;
va_list args;
va_start(args, format);
buffer = __HYP vsformat(format, args);
va_end(args);
return buffer;
}
char * __HYP vsformat(const char * format, va_list args)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
int size = vsnprintf(NULL, 0, format, args);
if (size <= 0) {
return NULL;
}
char * buffer = new char[size + 1];
if (buffer == NULL) {
return NULL;
}
if (vsnprintf(buffer, static_cast<size_t>(size), format, args) <= 0) {
free(buffer);
return NULL;
}
#pragma GCC diagnostic pop
return buffer;
}
I've been finding out how forgetful I am about C++, after a few years without touching it.
You are interested in the %r format that exists since ~1980 on UNOS and on DECUS.
See: http://austingroupbugs.net/view.php?id=800
There is a 30+ years old implementation in libschily, see: `http://sourceforge.net/projects/schilytools/files/ and a not-yet published implementation as an enhancement to libc on Solaris.
%r permits any printf() based function to be called by a wrapper function that then can offer the full printf() feature set.
%r takes two arguments:
1) a format string
2) a va_list type parameter
It needs an enhancement to the vararg macros called: va_arg_list() similar to and for the same reason why Sun/Solaris had to introduce va_copy() in the early 1990s in order to implement %n$.
BTW: There have been discussions about %r in the usenet around 1985 but the people that have been discussing the feature at that time believed it was not portable. My code has been tested on all existing CPU types and I am interested to have this added to the ISO C standard.
Here is some example code:
int
error(const char *fmt, ...)
{
va_list args;
int ret;
va_start(args, fmt);
ret = js_fprintf(stderr, "%r", fmt, args);
va_end(args);
return (ret);
}
I want to be able to create a template string and then use it like this:
int execute_command(char *cmd) {
//...
}
char *template_command = "some_command %s some_args %s %d";
char *actual_command = template_command % (cmd1, arg1, 123);
// error, how do I do that?
int res = execute_command(actual_command);
If you know the maximum length of actual_command, then you can use either one of the following:
char actual_command[MAX_LEN+1];
// Option #1
sprintf(actual_command, template_command, cmd1, arg1, 123);
// Option #2
snprintf(actual_command, MAX_LEN+1, template_command, cmd1, arg1, 123);
If MAX_LEN is not defined correctly, then:
Option #1 will result with undefined behavior (possibly runtime exception)
Option #2 will result with incorrect result (contents of actual_command)
Use snprintf and malloc (snprint returns the length of the string it would have written was the buffer only big enough, and receives the size of the buffer). POSIX asnprintf packages that nicely.
If you don't have it, define your own like this:
char* my_asnprintf(const char* format, ...) {
va_list arg;
va_start(arg, format);
size_t n = 1 + vsnprintf((char*)format, 0, format, arg);
va_end(arg);
char* ret = malloc(n);
if(!ret)
return ret;
va_start(arg, format);
vsnprintf(ret, n, format, arg);
va_end(arg);
return ret;
}
Don't forget to free the buffer.
I want to set a placeholder value (type int) of a path:
/sys/class/gpio/gpio%d/value => /sys/class/gpio/gpio33/value
The inserted value is maximum 99 and minimum 1. Because I do not want any empty chars in my path I would like to have the buffer size determined automatically.
This is why I thought of asprintf() which does this for strings unfortunately it does not work with integers.
#define GPIO_PATH_VALUE "/sys/class/gpio/gpio%d/value"
char * path;
asprintf(path, GPIO_PATH_VALUE, 4);
asprintf(path, GPIO_PATH_VALUE, 67);
Is there a function similar to asprintf() which works with integers?
Bodo
try this asPrintf() takes char ** as arguement, see this http://linux.die.net/man/3/asprintf
#define GPIO_PATH_VALUE "/sys/class/gpio/gpio%d/value"
char * path;
asprintf(&path, GPIO_PATH_VALUE, 4);
asprintf(&path, GPIO_PATH_VALUE, 67);
As asPrintf() do malloc() inside function, in this function path wont be pointing to malloced memory address, so you need to send address of path so that asPrintf() changes the path and that will be pointing to malloced address.
As asprintf() is a GNU extension, other people facing this problem might want to avoid it.
Instead, it could be done such as
#define GPIO_PATH_VALUE "/sys/class/gpio/gpio%d/value"
char * path;
path = malloc(strlen(GPIO_PATH_VALUE) + 5);
// error checking needed!
sprintf(path, GPIO_PATH_VALUE, 4); // better snprintf?
// or
sprintf(path, GPIO_PATH_VALUE, 67);
path = realloc(path, strlen(path)+1);
// no error checking needed, as we definitely shrink or nop, not extend.
if it is clear that GPIO_PATH_VALUE stays that simple.
If it gets more complicated, you can do
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
char * vbsprintf(const char * format, va_list ap)
{
va_list ap2;
va_copy(ap2, ap);
int len = vsnprintf(NULL, 0, format, ap2);
va_end(ap2);
char * str = malloc(len + 1);
if (!str) return NULL;
vsnprintf(str, len + 1, format, ap);
return str;
}
char * bsprintf(const char * format, ...)
{
va_list ap;
va_start(ap, format);
char * str = vbsprintf(format, ap);
va_end(ap);
return str;
}
if your system supports vsnprintf(NULL, 0, in order to determine the needed length.
I'm wondering if such a function exists:
void str_realloc_and_concat(char *str, const char *format, ...)
This function would take a char *str (allocated or NULL), and append to it *format.
I'm looking for something like a sprintf with realocation, strcpy and concatenation.
Does it exist or do I have to code it? Thanks for your inputs.
Update
The library has to be used on a embedded device so I don't want to use GNU extensions, since I'm not sure I'll have them.
Here is an implementation of such a function. To my knowledge the standard C library does not contain a function such as the one you are looking for. Notice that I would recommend to pass the target string as a double pointer, since realloc may change the pointer:
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
const char *prefix = "Hello";
void str_realloc_and_concat(char **str, const char *format, ...)
{
va_list ap;
int i, n;
char *s;
n = strlen(*str);
va_start(ap, format);
i = vasprintf(&s, format, ap);
*str = (char *)(realloc(*str, n + i + 1));
if (*str != NULL)
strncpy(&(*str)[n], s, i);
va_end(ap);
}
int main()
{
char *s = (char *)(malloc(strlen(prefix)));
strncpy(s, prefix, strlen(prefix));
str_realloc_and_concat(&s, " %s", "world!");
printf("%s\n", s);
return 0;
}
I decided to go with this. Hope it will help.
/**
* Reallocate a string and concatenate it with a formatted string.
* The src string has to be allocated by malloc/calloc.
* #param src Pointer to the original string
* #param format Format string
* #param ... Arguments that are to be formatted
*/
char *strcatf(char *src, const char *format, ...)
{
va_list ap, cp;
int format_length;
size_t translation_length;
char *dest;
if (NULL == src) {
return NULL;
}
va_start(ap, format);
va_copy(cp, ap);
translation_length = strlen(src);
format_length = vsnprintf(NULL, 0, format, cp);
if ((dest = realloc(src, (translation_length + format_length + 1) * sizeof(char))) == NULL) {
free(src);
} else {
vsprintf(&(dest)[translation_length], format, ap);
}
va_end(ap);
va_end(cp);
return dest;
}
This does not have the realloc part, but it does allocate. GNU libc's asprintf is like sprintf but allocates the result (to be big enough).
char *res = NULL;
const char *str = "other string";
int ret = asprintf(&res, "Hi %d %s\n", 2, str);
Concatenation is just a special case of using the format string.. if I understand correctly.