Создание строки формата с переменным количеством спецификаторов - PullRequest
0 голосов
/ 02 августа 2020

Я пытаюсь использовать va_list и связанные с ним макросы с vsprintf () для создания строки формата с переменным количеством спецификаторов. Вот пример программы, которую я написал, в которой количество спецификаторов может быть изменено только с помощью макроса NUM_ARG:

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

Однако это не совсем то, чего я хочу достичь. Как я мог сделать это с переменным количеством аргументов? Как можно передать переменное количество строк в функцию и использовать для инициализации va_list?

Ответы [ 2 ]

1 голос
/ 02 августа 2020

Насколько я знаю, это невозможно. Если вы не очень заинтересованы в использовании функций Variadi c и можете переопределить функцию. Приведенный ниже код соответствует вашим потребностям; Перебрать каждый элемент в массиве и добавить к строке, используя 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;
}

Терминальный сеанс:

$ ./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)
1 голос
/ 02 августа 2020

Короткий ответ: вы не можете.

Однако вы можете обойти это, используя массивы строк, возможно, динамически распределенные. Тогда вы могли бы использовать тот же метод, что и сейчас, но вместо этого перебирать массив.

Возможно что-то вроде этого:

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

Для аргументов командной строки в argv вам действительно не нужно создавать новый массив для их хранения, но он демонстрирует, как создать массив строки для передачи в strmaker. Вы можете использовать любые строки вместо аргументов командной строки.

...