Как рассчитать длину вывода, который будет генерировать sprintf?

цель: сериализация данных в JSON.

вопрос: Я не могу знать заранее, сколько символов длинное целое число.

Я думал, что хороший способ сделать это с помощью sprintf()

size_t length = sprintf(no_buff, "{data:%d}",12312);
char *buff = malloc(length);
snprintf(buff, length, "{data:%d}",12312);
//buff is passed on ...

конечно, я могу использовать переменную стека, как char a[256] вместо no_buff.

вопрос: но есть ли в C утилита для одноразовых записей, таких как unix /dev/null? Что-то вроде это:

#define FORGET_ABOUT_THIS ...
size_t length = sprintf(FORGET_ABOUT_THIS, "{data:%d}",12312);

p.s. я знаю,что я также могу получить длину целого числа через журнал, но это кажется лучше.

5 ответов


поскольку C-это простой язык, нет такой вещи, как "одноразовые буферы" - все управление памятью находится на плечах программистов (для них есть расширения компилятора GNU C, но они не являются стандартными).

не могу знать заранее, сколько символов длинное целое число.

существует гораздо более простое решение для вашей проблемы. snprintf знает!

на C99-совместимых платформах вызовите snprintf с NULL как первый аргумент:

ssize_t bufsz = snprintf(NULL, 0, "{data:%d}",12312);
char* buf = malloc(bufsz + 1);
snprintf(buf, bufsz + 1, "{data:%d}",12312);

...

free(buf);

в более старых версиях Visual Studio (которые имеют CRT, не совместимый с C99) используйте _scprintf вместо snprintf(NULL, ...) звонок.


можно назвать int len = snprintf(NULL, 0, "{data:%d}", 12312) чтобы проверить, сколько места вам нужно.

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

в этот момент Вы можете выделить и распечатать буфер, не забывая включить еще один для трейлинга '':

char *buf = malloc(len + 1);
snprintf(buf, len + 1, "{data:%d}", 12312);

чтобы просто получить длину вы можете написать:

int length = snprintf(NULL, 0, "{data:%d}", 12312);

обратите внимание, что тип возврата -int. Он может вернуться -1 в случае какой-то ошибки. Убедитесь, что входные данные не содержат длинных строк, Общая длина которых может превышать INT_MAX !


Если вы проверите производительность, то запуск snprintf без выходного буфера займет примерно столько же времени, сколько и полный вызов.

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

Это использует C++'ы std::string но я думаю, вы можете адаптировать его для своих нужд.

std::string format(const char* format, ...) {
    va_list args;
    va_start(args, format);
    char smallBuffer[1024];
    int size = vsnprintf(smallBuffer, sizeof smallBuffer, format, args);
    va_end(args);

    if (size < sizeof smallBuffer) 
        return std::string(smallBuffer);

    char buffer[size  + 1]; /* maybe malloc if it's too big */

    va_start(args, format);
    vsnprintf(buffer, sizeof buffer, format, args);
    va_end(args);
    return std::string(buffer);
}

этот код будет работать на 2x быстрее для строк под 1k по сравнению с более длинным те.


Это не совсем ответ на ваш вопрос, но вы, тем не менее может оказаться полезным. Это не портативный, но если вы используете это на glibc, вы можете просто использовать asprintf() вместо этого, который будет делать выделение памяти для вас.