Concatenating a pointer to a string literal in a Macro - c

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.

Related

Prepend variadic arguments and pass to another function

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

How to use a variadic argument for its own data structure?

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

Program: String concatenation using concept of variable arugment functions

Hi I am at beginner level and I am using concept of variable argument functions to concatenate strings. Same function is called for different number of strings.
I am not able to calculate the length of the concatenated string which in turn means i am not allocating memory properly. My dear peers, please help!
/* Program to do string concatenation using the concept of variable arguments */
/********************************************************************************
* REQUIRED HEADER FILES
*********************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
/********************************************************************************
* REQUIRED MACROS DEFINED
*********************************************************************************/
#define ERROR_CHECKER(result)\
if(result == FAILURE)\
{\
printf("\n CONCATENATION FAILED");\
}
typedef enum {SUCCESS = 0, FAILURE = 1} return_type;
/********************************************************************************
* REQUIRED FUNCTION PROTOTYPES
*********************************************************************************/
return_type string_concat(char* string_pointer, ...);
/********************************************************************************
*
* FUNCTION_NAME : STRING_CONCAT
*
* DESCRIPTION : concatenates incoming strings and displays the result
*
* RETURNS : SUCCESS OR FAILURE
*
*********************************************************************************/
return_type string_concat(
char* string_pointer,
...)
{
/********************************************************************************
* REQUIRED DECLARATIONS
*********************************************************************************/
// 1. arg_list that will point to variable number of arguments
va_list arg_list;
// 2. pointer to concatenated string
char* concatenated_string;
// 3. character pointer to point to an individual element in the argument list
char* individual_string_pointer;
// 4. amount of memory required to be allocated
int length;
/*********************************************************************************
* REQUIRED INITIALIZATIONS
*********************************************************************************/
va_start(arg_list, string_pointer);
concatenated_string = NULL;
individual_string_pointer = string_pointer;
length = 0;
/*********************************************************************************
* PERFORMING REQUIRED TASKS
**********************************************************************************/
// 1. calculate length till you reach quit
while(strcmp(individual_string_pointer,"quit") == 0)
{
individual_string_pointer = va_arg(arg_list, char*);
length = length + strlen(individual_string_pointer);
}
// individual_string_pointer reinitialized to be used for concatenation
individual_string_pointer = string_pointer;
printf("\nlength of concatenated string : %d", length);
// 2. allocate memory for the concatenated string
concatenated_string = (char*) malloc(sizeof(char) * length + 1);
// 3. use strncpy to copy first string and then use strncat to concatenate others
strncpy(concatenated_string, string_pointer, sizeof(*(string_pointer)));
while(strcmp(individual_string_pointer, "quit") == 0)
{
individual_string_pointer = va_arg(arg_list, char*);
strncat(concatenated_string, individual_string_pointer, sizeof(*(individual_string_pointer)));
}
printf("\n concatenated string : %s",concatenated_string);
va_end(arg_list);
return SUCCESS;
}
/********************************************************************************
*
* FUNCTION_NAME : MAIN
*
* DESCRIPTION : CALLS STRING_CONCAT FUNCTION
*
* RETURNS : SUCCESS
*********************************************************************************/
int main(void)
{
/********************************************************************************
* REQUIRED DECLARATIONS
*********************************************************************************/
// 1. character array as the first argument
char string_one[5] = "hello" ;
// 2. variable to store result from the string_concat function.
int result;
/*********************************************************************************
* REQUIRED INITIALIZATIONS
**********************************************************************************/
result = 0;
/*********************************************************************************
* PERFORMING REQUIRED TASKS
**********************************************************************************/
// 1. call string_concat function with 2 arguments
result = string_concat(string_one, "my", "name","is","amninder","quit");
// handle error from string_concat
ERROR_CHECKER(result);
// 2. call string_concat function with 3 arguments
result = string_concat(string_one, "I", "Like","fruits","quit");
// handle error from string_concat
ERROR_CHECKER(result);
// 3. call string_concat function with 4 arguments
result = string_concat(string_one, "awesome","quit");
// handle error from string_concat
ERROR_CHECKER(result);
/* doubt: do I need to send my first argument as same always " */
return SUCCESS;
}
Besides other issues: This sizeof(*(individual_string_pointer))); returns the size of what individual_string_pointer points to, namely a char, so it returns 1.
Either use strlen(ndividual_string_pointer) instead, or just switch to using strcat(), like this:
strcat(concatenated_string, individual_string_pointer)
Your first problem is that you are not doing anything with the data. Your second is an annonying rule preventing iteration over argument lists twice.
Return the concatenated string as malloced memory, or 0 on fail
char *concat(const char *first, ...)
{
char *answer;
int lenght;
va_list va;
char *nest;
int nextlen;
length = strlen(first);
answer = malloc(length + 1);
if(!answer)
goto out_of_memory;
strcpy(first, answer);
va_start(va, &first);
do
{
next = va_arg(va, char *);
if(!strcpy(nest, "quit))
break;
nextlen = strlen(next);
temp = realloc(answer, length + nextlen+1);
if(!temp)
goto out_of_memory;
answer = temp;
strcpy(answer, next);
length += nextlen;
} while(1);
va_end(va);
return answer;
out_of_memory:
free(answer);
return 0;
}
Rather than iterate the va_list twice, consider realloc() and append each sub-string. Additional ideas in comments.
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
#define return_type int
#define FAILURE -1
#define SUCCESS 0
// use const
return_type string_concat(const char* string_pointer, ...) {
va_list arg_list;
va_start(arg_list, string_pointer);
char* concatenated_string = NULL;
size_t length = 0; // Use type size_t, not int
const char* individual_string_pointer = string_pointer;
while (strcmp(individual_string_pointer, "quit")) {
// Find sub-string length _once_
size_t individual_length = strlen(individual_string_pointer);
size_t new_length = length + individual_length;
char *new_ptr = realloc(concatenated_string, new_length + 1);
if (new_ptr == NULL) {
free(concatenated_string); // do not forget to free old string
printf("\n MALLOC FALIED");
return FAILURE;
}
concatenated_string = new_ptr;
// or use memcpy(concatenated_string + length,
// individual_string_pointer, individual_length+1)
strcpy(concatenated_string + length, individual_string_pointer);
length = new_length;
individual_string_pointer = va_arg(arg_list, const char*);
}
va_end(arg_list);
// Add <> to detect leading/trailing white-space
printf("Concatenated string : <%s>\n", concatenated_string);
free(concatenated_string);
return SUCCESS;
}
int main(void) {
// not [5], let compiler size it to include the null character
char string_one[] = "hello";
string_concat(string_one, "my", "name", "is", "amninder", "quit");
string_concat(string_one, "I", "Like", "fruits", "quit");
return 0;
}
Ouput
Concatenated string : <hellomynameisamninder>
Concatenated string : <helloILikefruits>
Your approach is not bad, but could be improved, "quit" is not very safe to mark the end of va_list, NULL is a better choice. Because the user can make a mistake in "quit" like "qui", "qit" or whatever. And how concat "quit"? if you use string_concat without knowing what is inside the function could stop early when one of the strings is "quit":
char *string1 = "foo";
char *string2 = "quit";
char *string3 = "bar";
string_concat(string1, string2, string3, "quit");
Only "foo" will be used.
You don't return the string generate so your function is not really useful.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#define END_STRING_CONCAT ((char const *)NULL)
char *string_concat(char const *first, ...);
char *string_concat(char const *first, ...) {
va_list ap;
va_start(ap, first);
va_list cp_ap;
va_copy(cp_ap, ap); // copy for future use
size_t size = 1; // need the size of the future string
for (char const *ptr = first; ptr != NULL; ptr = va_arg(ap, char const *)) {
size += strlen(ptr);
}
va_end(ap);
char *result = malloc(size);
if (result == NULL) {
va_end(cp_ap);
return NULL;
}
size_t used = 0;
for (char const *ptr = first; ptr != NULL; ptr = va_arg(cp_ap, char const *)) {
size_t len = strlen(ptr);
if (size < used || size - used < len) {
free(result);
va_end(cp_ap);
return NULL;
}
memcpy(result + used, ptr, len); // use memcpy because it's faster in this case
used += len;
}
va_end(cp_ap);
if (size < used || size - used != 1) {
free(result);
return NULL;
}
result[used] = '\0'; // don't forget
return result;
}
int main(void) {
char hello[] = "hello, ";
char *result1 = string_concat(hello, "my ", "name ", "is ", "amninder",
END_STRING_CONCAT);
if (result1 != NULL) {
printf("%s\n", result1);
free(result1);
}
char *result2 = string_concat(hello, "I ", "Like ", "fruits", END_STRING_CONCAT);
if (result2 != NULL) {
printf("%s\n", result2);
free(result2);
}
char *result3 = string_concat(hello, "awesome", END_STRING_CONCAT);
if (result3 != NULL) {
printf("%s\n", result3);
free(result3);
}
return 0;
}

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

Does a function like this exist? void str_realloc_and_concat(char *str, const char *format, ...)

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.

Resources