I am trying to use va_list & its associated macros with vsprintf() to create a format string that has a variable number of specifiers. Here is an example program I wrote in which the number of specifiers can only be altered via the NUM_ARG macro:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#define MAXBUF 4096
#define SPECIFIER "(%s)"
#define NUM_ARG 5
char *strmaker(int num_args, ...)
{
char form[MAXBUF] = { [0] = '0円' };
char *prnt = (char *) malloc(sizeof(char) * MAXBUF);
va_list strings;
for (int i = 0; i < num_args; ++i)
strcat(form, SPECIFIER);
va_start(strings, num_args);
vsprintf(prnt, form, strings);
va_end(strings);
return prnt;
}
int main(int argc, char *argv[])
{
if (argc != (NUM_ARG + 1))
return -1;
char *s = strmaker(NUM_ARG, argv[1], argv[2], argv[3], argv[4], argv[5]);
printf("%s\n", s);
free(s);
return 0;
}
However, this isn't exactly what I want to achieve. How could I do this with a variable number of arguments? How could a variable number of strings be passed to a function and used to initialise a va_list?
2 Answers 2
As far as I know, it is not possible to do that. If you are not so keen about using variadic functions and can redefine the function. The below code suits your need; Iterate through each item in the array and append to the string using snprintf.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXBUF 4096
#define SPECIFIER "(%s)"
char *strmaker(int num_args, char** strings)
{
char *prnt = (char *) malloc(sizeof(char) * MAXBUF);
int cur = 0;
/* Append the strings to the prnt buffer */
for (int i = 0; i < num_args; i++) {
int p_return = snprintf(prnt + cur, MAXBUF - cur, SPECIFIER, strings[i]); // If no error, return the number characters printed excluding nul (man page)
if (p_return >= MAXBUF - cur) // If buffer overflows (man page)
return prnt;
cur = cur + p_return; // Update the index location.
}
return prnt;
}
int main(int argc, char *argv[])
{
if (argc <= 1)
return -1;
char *s = strmaker(argc - 1, argv + 1);
printf("%s\n", s);
free(s);
return 0;
}
Terminal Session:
$ ./a.out 1 2 3
(1)(2)(3)
$ ./a.out 1 2 3 4 5 6 7
(1)(2)(3)(4)(5)(6)(7)
$ ./a.out Hello, This is stackoverflow, Bye
(Hello,)(This)(is)(stackoverflow,)(Bye)
2 Comments
Short answer is: You can't.
However you can work around it by using arrays of strings, possibly dynamically allocated. Then you could basically use the same technique you do now, but iterate over the array instead.
Perhaps something like this:
char *strmaker(size_t count, char *strings[])
{
// First get the length of all strings in the array
size_t result_length = 0;
for (size_t i = 0; i < count; ++i)
{
// +1 for space between the strings
// And for the last string adds space for the string null-terminator
result_length += strlen(strings[i]) + 1;
}
// Now allocate the string (using calloc to initialize memory to zero, same as the string null-terminator)
char *result = calloc(1, result_length);
// And not concatenate all strings in the array into one large string
for (size_t i = 0; i < count; ++i)
{
strcat(result, strings[i]);
if (i != count - 1)
{
strcat(result, " "); // Add space, except after last string
}
}
// Return the resulting string
return string;
}
int main(int argc, char *argv[])
{
// Create an array for all arguments
char **arguments = malloc(sizeof(char *) * argc - 1);
for (int a = 1; a < argc)
{
arguments[a - 1] = argv[a];
}
// Now create the single string
char *result = strmaker(argc - 1, arguments);
// ... and print it
printf("%s\n", result);
// Finally clean up after us
free(result);
free(arguments);
}
For the command-line arguments in argv you don't really need to create a new array to hold them, but it showcases how to create an array of string to pass to strmaker. You can use any strings you want instead of the command-line arguments.
2 Comments
Explore related questions
See similar questions with these tags.