запись отформатированных данных неизвестной длины в строку (программирование на C) - PullRequest
10 голосов
/ 06 декабря 2009

Следующая функция C:

int sprintf ( char * str, const char * format, ... );

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

Например:

#include <stdio.h>

int main ()
{
  char buffer [13];
  int n, a=5, b=3;
  n=sprintf (buffer, "%d plus %d is %d", a, b, a+b);
  printf ("[%s] is a %d char long string\n",buffer,n);
  return 0;
}

Буфер должен быть 13 или больше, чтобы это работало. Если длина строки была неизвестна, а буфер, например, был установлен на 5, это не будет работать. Мне нужно что-то, что может динамически распределять или перераспределять буфер для строк, которые оказываются больше, чем буфер.

Ответы [ 5 ]

17 голосов
/ 06 декабря 2009

Использование snprintf

Большинство людей скажет вам использовать snprintf(), потому что это не будет заполнять конец вашего буфера.

И это хороший совет. Обычный «шаблон проектирования» заключается в объявлении временного буфера фиксированного размера, который больше, чем строка, которая когда-либо может быть, и snprintf() к этому. Если строку необходимо сохранить на некоторое время, вы можете измерить ее длину, malloc(3) другую и strcpy(3) временный буфер для полупостоянного буфера malloc().


Двухпроходный подход

Есть другой способ.

C99 указывает, что если буфер равен NULL, то никакие байты не записываются, но возвращается фактическая длина, которая была бы записана. Это позволяет вам сделать фиктивный первый проход, malloc () буфер, а затем snprintf() к этому буферу. (Теоретически вы можете использовать обычный sprintf(), поскольку длина теперь известна, но я бы не стал.)

В любом случае, я не уверен, что рассчитывал бы на все это, если бы моей программе приходилось работать на всех ОС, когда-либо созданных в прошлом, но для большинства людей это разумная схема использования в наши дни.

8 голосов
/ 06 декабря 2009

То, что вы хотите, это одна из этих двух функций: * snprintf (http://libslack.org/manpages/snprintf.3.html). В качестве второго аргумента берется длина выходного буфера, а если буфер слишком мал для результата, он возвращает количество необходимых символов, что позволяет перераспределить больший буфер , * asprintf. Он принимает аргумент char ** и выделяет достаточно памяти для хранения вывода, если доступно столько смежной виртуальной памяти. Вам нужно вызвать free, чтобы удалить его из памяти, если вы закончили с ним до выхода из программы, и вам может понадобиться память для чего-то другого.

2 голосов
/ 06 декабря 2009

Я собрал это вместе как упражнение для себя, основываясь на предложении DigitalRoss. Не стесняйтесь объединить это с его ответом, если у вас есть представитель (у меня нет).

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char* createRandstr(void);

int main(void)
{
    char* mystr;
    char* randomString;
    size_t len;

    /* Makes a random string of A's */
    randomString = createRandstr();

    /* 1st pass gets needed size */
    len = (size_t)snprintf(NULL, 0, "random string -->%s<--\n", randomString);
    mystr = malloc(len);

    /* Safely write to mystr with known length 'len' */
    snprintf(mystr, len, "random string -->%s<--\n", randomString);
    puts(mystr);

    free(mystr);
    free(randomString);

    return 0;
}

char* createRandstr(void)
{
    char*  randstr;
    size_t randlen;
    unsigned int i;

    srand((unsigned int)time((time_t*)NULL)); /* Seed rand() */
    randlen = (size_t)rand() % 50; /* Limit to max of 49 chars */
    randstr = malloc(randlen);

    for (i=0; i < randlen; i++)
    {
        randstr[i] = 'A';
    }
    randstr[randlen - 1] = '\0';

    return randstr;
}
1 голос
/ 06 декабря 2009

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

1 голос
/ 06 декабря 2009

Прежде всего, используйте snprintf вместо sprintf. snprintf принимает дополнительный аргумент, который является максимальным количеством байтов для записи (включая завершающий '\0'). snprintf затем возвращает количество записанных байтов, не включая завершающий '\0'. Таким образом, вы можете выделить буфер, который, по вашему мнению, будет достаточно большим, и попробовать вызов. Если он достиг конца буфера, освободите этот буфер, сделайте новый, больший и попробуйте снова. Промыть и повторить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...