Create a "template" (format) string - c

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.

Related

Dynamic formatted string from another function

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

vsnprintf and varargs not working, weird results

I'm using vsnprintf (as far as I know correctly) but am getting weird results. I've simplified my code down to the following example:
void func(char *aaa, ...)
{
char *buf;
va_list args;
int size;
va_start(args, aaa);
size = vsnprintf(NULL, 0, aaa, args)+1;
buf = malloc(size);
vsnprintf(buf, size, aaa, args);
printf("%s",buf);
free(buf);
va_end(args);
}
int main(int argc, char **argv)
{
func("abc %s", "def\n");
return 0;
}
I'd expect "abc def" to get printed, but instead I get "abc" followed by some garbage text. Does anyone have an idea of where I messed up?
va_start(args, aaa);
size = vsnprintf(NULL, 0, aaa, args)+1; // Reads all arguments
buf = malloc(size);
vsnprintf(buf, size, aaa, args); // Tries to read all arguments again
printf("%s",buf);
free(buf);
va_end(args);
See the commented lines. You have to reset args between those two calls consuming all arguments.
Insert:
va_end(args);
va_start(args, aaa);
When you pass the handle args to vsnprintf() it will change it internally in your case. If used again it will not give the correct arguments.
Use va_copy() to create a copy of args if you wish to get your optional arguments twice.
void func(char *aaa, ...)
{
char *buf;
va_list args , argsc ; //argsc is used in the second vsnprintf()
int size;
va_start(args, aaa);
va_copy( argsc , args ) ; //make a copy of args
size = vsnprintf(NULL, 0, aaa, args)+1;
buf = malloc(size);
vsnprintf(buf, size, aaa, argsc); //use the copy since args is not valid
printf("%s",buf);
free(buf);
va_end(args);
va_end(argsc); //destroy both
}

How to remove this warning: second parameter of ‘va_start’ not last named argument?

I have a function (see below) that is emitting the following warning:
second parameter of ‘va_start’ not last named argument
What does it means and how to remove it?
The function is as the following:
static int ui_show_warning(GtkWindow *parent, const gchar *fmt, size_t size, ...)
{
GtkWidget *dialog = NULL;
va_list args = NULL;
int count = -1;
char *msg = NULL;
if((msg = malloc(size + 1)) == NULL)
return -12;
va_start(args, fmt);
if((count = snprintf(msg, size, fmt, args)) < 0)
goto outer;
dialog = gtk_message_dialog_new(parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_OK,
"%s", msg);
(void) gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
outer: {
if(args != NULL)
va_end(args);
if(msg != NULL)
free(msg);
return count;
}
}
You need to use size instead of fmt:
va_start(args, size);
It is size, not fmt, that is the last parameter that has an explicit name (as opposed to vararg parameters, which have no names). You need to pass the last named parameter to va_start in order for it to figure out the address in memory at which the vararg parameters start.
second parameter of ‘va_start’ not last named argument
What does it means and how to remove it?
Your function has named parameters parent, fmt and size. The C spec says you have to always pass the last named parameter to va_start, for compatibility with older compilers. So you must pass size, not fmt.
(But with a modern compiler, it might work anyway)
I think there is a confusion here: most of people only deal with prinf-like functionsh which have format and varargs. and they think they have to pass parameter name which describes format. however va_start has nothing to do with any kind of printf like format. this is just a function which calculates offset on the stack where unnamed parameters start.
I have the same problem on Ubuntu20.04,Contrary to the answer with the most likes,
the code at the beginning,
void sprintf(char *str, char *fmt, ...) {
va_list list;
int i, len;
va_start(list, 2);
...
}
and then, the code as follows
void sprintf(char *str, char *fmt, ...) {
va_list list;
int i, len;
va_start(list, fmt);
...
}
Problem was sovled.

va_list misbehavior on Linux

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 = &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 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 = &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;
}
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);
...

Strange behavior (SEGFAULT) of a C program using stdargs (va_start)

I wrote a variadic C function which mission is to allocate the needed memory for a buffer, and then sprintf the args given to this function in that buffer. But I'm seeing a strange behavior with it. It works only once. If I have two calls for this function it segfaults.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
char *xsprintf(char * fmt, ...)
{
va_list ap;
char *part;
char *buf;
size_t len = strlen(fmt)+1;
va_start(ap, fmt);
while (part = va_arg(ap, char *))
len += strlen(part);
va_end(ap);
buf = (char*) malloc(sizeof(char)*len);
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
return buf;
}
int main(int argc, const char *argv[])
{
char *b;
b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
printf("size de buf is %d\n", strlen(b)); //this works. After it, it segfaults.
/*
free(b);
b = NULL;
*/
b = xsprintf("my favorite fruits are: %s, %s, and %s", "coffee", "C", "oranges");
printf("size de buf is %d\n", strlen(b));
printf("%s", b);
return 0;
}
here's the output of this program:
size de buf is 46
[1] 4305 segmentation fault ./xsprintftest
Am I doing something wrong? Shouldn't I have used va_start multiple times in a single function? Do you have any alternatives? Thanks a lot! :)
You should be using vsnprintf. Use it twice. Once with a NULL destination/zero size to find out the length of the buffer you need to allocate, then a second time to fill the buffer. That way your function will work even if all the arguments are not strings.
As written, it will fail if there are any non-string arguments (%d, %x, %f, etc.). And counting the number of % characters is not a valid way to get the number of arguments. Your result could be too many (if there are literal % characters encoded as %%) or too few (if arguments are also needed for %*s, %.*d, etc. width/precision specifiers).
Pass NULL as the last arg to xsprintf():
b = xsprintf("my favorite fruits are: %s, %s, and %s",
"coffee", "C", "oranges", (void*)0);
Then your while() loop will see the NULL and terminate properly.
As R.. mentions in the comment below and in another answer, the xsprintf function will fail if there are other format arguments. You are better off using vsprintf as explained in the other answer.
My intent here was simply to demonstrate the use of a sentinel with va_arg.
First off, try using vsnprintf. It's just a good idea.
That's not your problem, though. Your problem is that you can't call va_arg more times than there are arguments. It doesn't return the number of arguments. You must either pass in a parameter telling it how many there are, or extract the number of special tokens in the format string to figure out how many there must implicitly be.
That's the reason why printf can choke your program if you pass it too few arguments; it will just keep pulling things off the stack.
The problem is that in the bit of code where you're accessing the va_arg() list without a particular defined end:
va_start(ap, fmt);
while (part = va_arg(ap, char *))
len += strlen(part);
va_end(ap);
The stdargs.h facilities don't have any built-in method for determining when the end of the va_list() occurs - you need to have that explicitly done by a convention you come up with. Either using a sentinel value (as in bstpierre's answer), or by having a count provided. A count can be an explicit parameter that's provided, or it can be implicit (such as by counting the number of format specifiers in the format string like the printf() family does).
Of course, you also have the issue that your code currently only supports the one kind of format-specifier (%s), but I assumed that that's intentional at this point.
Thanks a lot for your answers and ideas! So I rewrote my function like this:
void fatal(const char *msg)/*{{{*/
{
fprintf(stderr, "program: %s", msg);
abort ();
}/*}}}*/
void *xmalloc(size_t size)/*{{{*/
{
register void *value = malloc(size);
if (value == 0)
fatal ("Virtual memory exhausted");
return value;
}/*}}}*/
void *xrealloc(void *ptr, size_t size)/*{{{*/
{
register void *value = realloc(ptr, size);
if (value == 0)
fatal ("Virtual memory exhausted");
return value;
}/*}}}*/
char *xsprintf(const char *fmt, ...)/*{{{*/
{
/* Heavily inspired from http://perfec.to/vsprintf/pasprintf */
va_list args;
char *buf;
size_t bufsize;
char *newbuf;
size_t nextsize;
int outsize;
int FIRSTSIZE = 20;
bufsize = 0;
for (;;) {
if(bufsize == 0){
buf = (char*) xmalloc(FIRSTSIZE);
bufsize = FIRSTSIZE;
}
else{
newbuf = (char *)xrealloc(buf, nextsize);
buf = newbuf;
bufsize = nextsize;
}
va_start(args, fmt);
outsize = vsnprintf(buf, bufsize, fmt, args);
va_end(args);
if (outsize == -1) {
/* Clear indication that output was truncated, but no
* clear indication of how big buffer needs to be, so
* simply double existing buffer size for next time.
*/
nextsize = bufsize * 2;
} else if (outsize == bufsize) {
/* Output was truncated (since at least the \0 could
* not fit), but no indication of how big the buffer
* needs to be, so just double existing buffer size
* for next time.
*/
nextsize = bufsize * 2;
} else if (outsize > bufsize) {
/* Output was truncated, but we were told exactly how
* big the buffer needs to be next time. Add two chars
* to the returned size. One for the \0, and one to
* prevent ambiguity in the next case below.
*/
nextsize = outsize + 2;
} else if (outsize == bufsize - 1) {
/* This is ambiguous. May mean that the output string
* exactly fits, but on some systems the output string
* may have been trucated. We can't tell.
* Just double the buffer size for next time.
*/
nextsize = bufsize * 2;
} else {
/* Output was not truncated */
break;
}
}
return buf;
}/*}}}*/
And it's working like a charm! Thanks a million times :)

Resources