0

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

1

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
  • Hmmm, could this work with a format string that has already been created? I.e. imagine a format string such as "he(%s)llo(%s)" (contains specifiers & text). I have attached a variable "count" to keep track of the specifiers. How would I then print the correct amount of strings to the format string using one of the printf functions? Commented Aug 2, 2020 at 20:46
  • @GabrielSaul Your question is already answered. If you have a new question, create a new question instead of updating the constraints in a comment like this. Thank you. Commented Aug 3, 2020 at 2:09
1

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
  • So, have an array of strings, iterate over it and call the function for each iteration? If I am therefore adding one string at a time to the format string, would the format string & the printed string have to be statically allocated? Commented Aug 2, 2020 at 15:31
  • @GabrielSaul Not quite what I meant. Added example to answer. Commented Aug 2, 2020 at 15:52

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.