I am having some trouble with a variadic printf-style function that I wrote, which works correctly when there are no variadic arguments but not when there are, e.g:
do_print("TestName", ""); works.
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice"); doesn't
I have done some debugging and I believe the issue is that the variadic arguments themselves are not getting processed as they should be. I looked at some examples and similar questions, and that has led me to believe that I'm misusing va_start and va_end in one of the functions. The format string that I'm generating is correct, but I'm getting a repeat for the first variadic arg of the name itself and then garbage values for the remaining variadic arguments:
Message:
Name:TestName
ID:42
Key1:TestName
Key2:-1
Key3:��-�*
(should be:)
Message:
Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
My thought was perhaps that I should eliminate a va_start / va_end and just pass the ap in directly, although in certain cases I call the fmt_print function directly so I'm not sure what I would pass in for ap then. That's what I was playing with fmt_print2 for, but right now that just segfaults and is even worse than the original.
And yes, I do need vasprintf. The code here is printf-style, but ultimately I do need the entire message in a single buffer because I then pass it to the write function, not a printf-style function, so fmt_print does need to do the work of combining everything into a single buffer.
Here is a full minimal reproducible example, which I've been testing here: https://www.onlinegdb.com/online_c_compiler
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
static int fmt_print2(va_list ap, const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
//va_start(ap, fmt);
if ((len = vasprintf(&buf, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
//va_end(ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
return res;
}
#pragma GCC diagnostic pop
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[200];
snprintf(buf, sizeof(buf), "%s%s%s", "Name:%s\r\nID:%d\r\n", fmt, "\r\n\r\n");
// send Name:%s\r\ID:%d\r\nKey1:%s\r\nKey2:%d\r\nKey3:%s fmt string + all args.
//fmt_print2(ap, buf, name, 42);
va_start(ap, fmt);
fmt_print(buf, name, 42);
va_end(ap);
}
int main()
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
// This is also valid:
fmt_print("Login", "Name:%s\r\nID:%d\r\nUsername:%s\r\nPassword:%s", "Login", 41, username, password);
return 0;
}
Could someone point me in the right direction of what approach is best suited for this?
As I said before in a comment, you can't do what you are trying to do the way you are trying to do it. The snprintf() in do_print() is trying to set a format string that requires two separate va_list values to process it. You will have to keep the two lots of formatting separate. And then, if necessary, combine the two strings.
This code works. It contains a lot of diagnostic printing which you can remove, but it showed me where things were going wrong. It includes the trailing \r\n\r\n as a separate operation.
/* SO 7274-3429 */
#define _GNU_SOURCE /* needed for vasprintf */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
{
char *buf1;
char *buf2;
int len1;
int len2;
va_list ap2;
char *buffer;
printf("%s(): fmt1 = [[%s]]\n", __func__, fmt1);
len1 = vasprintf(&buf1, fmt1, ap1);
printf("%s(): buf1 = [[%s]]\n", __func__, buf1);
printf("%s(): fmt2 = [[%s]]\n", __func__, fmt2);
va_start(ap2, fmt2);
len2 = vasprintf(&buf2, fmt2, ap2);
va_end(ap2);
printf("%s(): buf2 = [[%s]]\n", __func__, buf2);
if (len1 < 0 || len2 < 0)
return -1;
printf("%s(): Format strings:\n[[%s]]\n[[%s]]\n", __func__, fmt1, fmt2);
printf("%s(): Message strings:\n[[%s]]\n[[%s]]\n[END]\n", __func__, buf1, buf2);
buffer = malloc(len1 + len2 + sizeof("\r\n\r\n"));
if (buffer == 0)
{
free(buf1);
free(buf2);
return -1;
}
strcpy(buffer, buf2);
strcpy(buffer + len2, buf1);
strcpy(buffer + len2 + len1, "\r\n\r\n");
printf("Full buffer: [[%s]]\n", buffer);
free(buf1);
free(buf2);
free(buffer);
return 0;
}
static void do_print(const char *name, const char *fmt, ...)
{
va_list ap;
char buf[] = "Name:%s\r\nID:%d\r\n";
va_start(ap, fmt);
fmt_print(fmt, ap, buf, name, 42);
va_end(ap);
}
int main(void)
{
do_print("TestName", "Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
return 0;
}
The output from it is:
fmt_print(): fmt1 = [[Key1:%s
Key2:%d
Key3:%s]]
fmt_print(): buf1 = [[Key1:Bob
Key2:4
Key3:Alice]]
fmt_print(): fmt2 = [[Name:%s
ID:%d
]]
fmt_print(): buf2 = [[Name:TestName
ID:42
]]
fmt_print(): Format strings:
[[Key1:%s
Key2:%d
Key3:%s]]
[[Name:%s
ID:%d
]]
fmt_print(): Message strings:
[[Key1:Bob
Key2:4
Key3:Alice]]
[[Name:TestName
ID:42
]]
[END]
Full buffer: [[Name:TestName
ID:42
Key1:Bob
Key2:4
Key3:Alice
]]
Note that this code frees the memory allocated by vasprintf().
Since you try to compile with -Wmissing-format-attribute, you will still get errors from a verbatim copy of this code. It will compile cleanly with your options if you add:
#if !defined(PRINTFLIKE)
#if defined(__GNUC__)
#define PRINTFLIKE(n, m) __attribute__((format(printf, n, m)))
#else
#define PRINTFLIKE(n, m) /* If only */
#endif /* __GNUC__ */
#endif /* PRINTFLIKE */
and change the function definition lines to:
static int PRINTFLIKE(3, 4) fmt_print(const char *fmt1, va_list ap1, const char *fmt2, ...)
and
static void PRINTFLIKE(2, 3) do_print(const char *name, const char *fmt, ...)
and in do_print(), modify the call to fmt_print() to take a literal format (eliminating the variable buf):
fmt_print(fmt, ap, "Name:%s\r\nID:%d\r\n", name, 42);
A lot of the time, the 'non-literal format' check is helpful and sensible, but there are times when it is counter-productive.
You simply forgot to allocate memory for your fmt_print function.
static int fmt_print2(const char *fmt, va_list ap);
static int fmt_print(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
printf("LEN = %d\n", len);
va_start(ap, fmt);
if(buf)
{
vsnprintf(buf, len, fmt, ap);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
va_end(ap);
return res;
}
static int fmt_print1(const char *fmt, ...)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list ap;
va_start(ap, fmt);
fmt_print2(fmt, ap);
va_end(ap);
return res;
}
static int fmt_print2(const char *fmt, va_list ap)
{
int res = 0;
int bytes = 0;
char *buf;
int len;
va_list copy;
va_copy(copy, ap);
if ((len = vsnprintf(NULL, 0, fmt, ap)) < 0) {
va_end(ap);
return -1;
}
buf = malloc(len + 1);
if(buf)
{
vsnprintf(buf, len, fmt, copy);
printf("FORMAT STRING: %s\n", fmt);
printf("Message:\n%s[END]", buf);
}
free(buf);
return res;
}
int main()
{
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "Bob", 4, "Alice");
fmt_print("Key1:%s\r\nKey2:%d\r\nKey3:%s", "AAAA", 8, "BBBB");
}
Related
I need to append the function name of the caller concatenated wit brackets at the beginning of the string so it looks like [<FUNCTION_NAME>] <string> but rather I am getting [name] <string> as an output...possibly it has to do with # stringifying the variable passed in but how do you get it to work?
#define FUN(STR) "["#STR"] "
void foo(const char *name, char *fmt, ...)
{
char buff[100] = {0};
va_list pList;
va_start(pList, fmt);
int ret = vsprintf(buff, fmt, pList);
va_end(pList);
char p[100] = {0};
sprintf (p, "%s%s", FUN(name), buff);
printf ("%s\n", p); // [name] Some string with value = 5
}
void bar()
{
foo(__func__, "Some string with value = %d", 5);
}
int main()
{
bar();
return 0;
}
Just put the brackets in printf format string.
printf("[%s]%s", name, buff)
But it sounds like a waste to have two buffers. Just have one buffer. Also, the code needs protection against overload. Needed checks need to be added to protect against overflowing the buffer. Also, prefer to use snprintf over sprintf.
#define MIN(a, b) ((a)<(b)?(a):(b))
#ifdef __GNUC__
#if __GNUC__ > 10
__attribute__((__access__(read_only, 1)))
#endif
__attribute__((__format__(__printf__, 2, 3)))
#endif
void foo(const char *name, char *fmt, ...)
{
char buff[10];
char *p = buff;
const char *const end = buff + sizeof(buff);
// Add bracket.
static_assert(sizeof(buff) > 0, "");
*p++ = '[';
// Add name.
const size_t namelen = strlen(name);
size_t free = end - p;
// Leave space for 0 either way.
const size_t tocopy = MIN(free - 1, namelen);
memcpy(p, name, tocopy);
p += tocopy;
// Add "] ". Wonder, maybe it should be <=, not sure.
if (p < end - 1) {
*p++ = ']';
}
if (p < end - 1) {
*p++ = ' ';
}
// Add printf stuff
free = end - p;
if (free > 1) {
// It's odd that there is no vfoo(..., va_list va); overload.
va_list va;
va_start(va, fmt);
const int r = vsnprintf(p, free + 1, fmt, va);
va_end(va);
(void)r;
assert(r >= 0); // unneeded, pedantic: check errors
} else {
// Och, we remember about it.
assert(p < end);
*p = 0;
}
printf("%s\n", buff);
}
why would a macro approach not work?
In short: Macros work on text. Variables do not exist for preprocessor, only other macros. # applies # operation on text name, so it becomes "name", in effect the call becomes;
sprintf (p, "%s%s", "[" "name" "] ", buff);
Adjacent "this" "that" string literals are concatenated together into one "thisthat", and %s just prints it all.
To facilitate the use of a data structure that contains a string to be filled from a function, I would like to be able to define the same function with variadic arguments, like this:
struct my_struct_t
{
char *msg;
};
struct my_struct_t *fill(const char *fmt, ...);
struct my_struct_t *filled = fill("A number: %d, a string: '%s'.", 43, "hello");
To do so, I implemented the following function, as well as variants, but the result was always wrong when I retrieved the string in the structure. Here is the code:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
struct my_struct_t
{
char *msg;
};
struct my_struct_t *fill(const char *fmt, ...)
{
va_list ap1, ap2;
va_copy(ap2, ap1);
va_start(ap1, fmt);
int slen = snprintf(NULL, 0, fmt, ap1);
va_end(ap1);
char *str = malloc(slen);
assert(str != NULL);
va_start(ap2, fmt);
snprintf(str, slen, fmt, ap2);
va_end(ap2);
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
assert(my_struct != NULL);
my_struct->msg = str;
return my_struct;
}
int main()
{
struct my_struct_t *filled = fill("A number: %d, a string: '%s'.", 43, "hello");
printf("%s\n", my_struct->msg);
return 0;
}
This leads to different results with each execution, for example:
A number: 7011816, a string: 'ðe'
I guess this is a problem with the use of variadic arguments, however I haven't found how to solve my problem, i.e. save the string in the structure field with the formatting sent, so I'll expect this:
A number: 43, a string: 'hello'.
A quick possible fix. Notes in code
struct my_struct_t *fill(const char *fmt, ...) {
va_list ap1, ap2;
// change order
va_start(ap1, fmt);
va_copy(ap2, ap1);
// Use vsnprintf
//int slen = snprintf(NULL, 0, fmt, ap1);
int slen = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
// test result
assert(slen >= 0);
// ... or a pedantic test
assert(slen >= 0 && (unsigned) slen < SIZE_MAX);
// Need + 1 for null character
// char *str = malloc(slen);
char *str = malloc(slen + 1u);
assert(str != NULL);
// No va_start, copy is enough
// va_start(ap2, fmt);
// snprintf(str, slen, fmt, ap2);
// Since we a going for broke, no need for `n`, pedantically we could/should use vsnprintf()
// vsnprintf(str, slen+1u, fmt, ap2);
vsprintf(str, fmt, ap2);
va_end(ap2);
// Good use of sizing by referenced type
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
assert(my_struct != NULL);
my_struct->msg = str;
return my_struct;
}
Rather than assert(), code could return NULL. Be sure to free resources.
struct my_struct_t *fill(const char *fmt, ...) {
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1);
int slen = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
if (slen < 0 || (unsigned) slen >= SIZE_MAX) {
va_end(ap2);
return NULL;
}
char *str = malloc(slen + 1u);
if (str == NULL) {
va_end(ap2);
return NULL;
}
slen = vsnprintf(str, slen+1u, fmt, ap2);
va_end(ap2);
if (slen < 0 || (unsigned) slen >= SIZE_MAX) {
free(str);
return NULL;
}
struct my_struct_t *my_struct = malloc(sizeof *my_struct);
if (my_struct == NULL) {
free(str);
return NULL;
}
my_struct->msg = str;
return my_struct;
}
I'm introducing a 3rd party protocol stack to on an old embeded platform where all va_* stuff are implemented except va_copy. The problem I face is, in the 3rd party stack, vsnprintf() is used:
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
va_list ap_copy;
int len;
/* first call*/
va_copy(ap_copy, ap);
len = vsnprintf(*buf, buf_size, fmt, ap_copy);
va_end(ap_copy);
if(len >= buf_size)
{
/* 2nd call*/
va_copy(ap_copy, ap);
len = vsnprintf(*buf, len + 1, fmt, ap_copy);
va_end(ap_copy);
}
}
Luckily the 3rd party stack provedes its own vsnprintf function(call it new_vsnprintf), but without va_copy, only the first call works, i.e, when the len is small than buf_size. Below is the way I call it:
#define vsnprintf new_vsnprintf
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
//va_list ap_copy;
int len;
/* first call*/
va_start(ap, fmt);
len = vsnprintf(*buf, buf_size, fmt, ap);
va_end(ap);
if(len >= buf_size)
{
/* 2nd call*/
va_start(ap, fmt);
len = vsnprintf(*buf, len + 1, fmt, ap); //new_vsnprintf()
va_end(ap);
}
}
Problem occurs in the 2nd call of new_vsnprintf(), when trying to get the actual value of placeholders by va_arg(). I assume the inner pointer of (va_list) ap points to wrong memory address.
Then how to correct it?
You shouldn't be calling either va_start or va_end in your function fun. They should only be called in functions that have ... in the argument list to indicate that there are a variable number of arguments. My version of gcc (4.8.4) even throws up an error if I try and compile your code as it currently exists.
test.c: In function ‘fun’:
test.c:14:3: error: ‘va_start’ used in function with fixed args
va_start(ap, fmt);
It seems likely that calling them in functions with fixed args is causing undefined behaviour and it's luck that the first call to vsnprintf is actually working.
Since you're not calling va_arg inside fun the value of ap shouldn't be changing and you should be able to call vsnprintf as many times as you require.
This answer may work if your platform has a simple varargs handling, like most platform do. If your platform do not permit va_list object to be trivially copied this cannot work. On most platforms va_copy is a simple macro that does some thing like:
#define va_copy(dest, src) dest = src
... this is the case for x86, ppc and many other targets. Anyway here is the code, I hope it works, I've no way to test or confirm it works without knowing the platform you are using it on!
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
va_list ap_copy = ap;
int len;
/* first call*/
va_start(ap_copy, fmt);
len = vsnprintf(*buf, buf_size, fmt, ap_copy);
va_end(ap_copy);
if(len >= buf_size)
{
/* 2nd call*/
va_start(ap, fmt);
len = vsnprintf(*buf, len + 1, fmt, ap); //new_vsnprintf()
va_end(ap);
}
}
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
}
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.