Подтвердить что ты не робот

Есть ли способ определить, сколько символов будет написано sprintf?

Я работаю на С++.

Я хочу написать потенциально очень длинную форматированную строку, используя sprintf (в частности, надежную подсчитанную версию, такую ​​как _snprintf_s, но идея такая же). Пример приблизительной длины неизвестен во время компиляции, поэтому мне придется использовать некоторую динамически распределенную память, а не полагаться на большой статический буфер. Есть ли способ определить, сколько символов потребуется для конкретного вызова sprintf, поэтому я всегда могу быть уверен, что у меня есть достаточно большой буфер?

Мой отказ - я просто возьму длину строки формата, удвою ее и попробую. Если он работает, отлично, если нет, я просто удвою размер буфера и повторю попытку. Повторяйте, пока он не подходит. Не совсем лучшее решение.

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

Может быть, fprintf для "/dev/null" / "nul" может работать? Любые другие идеи?

EDIT: Альтернативно, есть ли способ "обрезать" sprintf, чтобы он забирал среднюю запись? Если это возможно, он может заполнить буфер, обработать его, а затем начать заполнять с того места, где он остановился.

4b9b3361

Ответ 1

В справочной странице snprintf говорится:

   Return value
       Upon  successful  return,  these  functions return the number of
       characters printed (not including the trailing '\0' used to  end
       output to strings).  The functions snprintf and vsnprintf do not
       write more than size bytes (including the  trailing  '\0').   If
       the output was truncated due to this limit then the return value
       is the number of characters (not including  the  trailing  '\0')
       which  would  have  been  written  to the final string if enough
       space had been available. Thus, a return value of size  or  more
       means  that  the  output  was  truncated.  (See also below under
       NOTES.)  If an output error is encountered, a negative value  is
       returned.

Это означает, что вы можете вызывать snprintf с размером 0. Ничто не будет записано, и возвращаемое значение укажет вам, сколько места вам нужно выделить для вашей строки:

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...);

Ответ 2

Как отмечали другие, snprintf() вернет количество символов, необходимых в буфере, чтобы предотвратить усечение вывода. Вы можете просто вызвать его с помощью параметра длины буфера 0, чтобы получить требуемый размер, затем используйте буфер соответствующего размера.

Для небольшого повышения эффективности вы можете вызвать его с буфером, достаточно большим для нормального случая, и сделать второй вызов snprintf(), если вывод усечен. Чтобы убедиться, что буфер правильно освобожден в этом случае, я часто использую объект auto_buffer<>, который обрабатывает динамическую память для меня (и имеет буфер по умолчанию в стеке, чтобы избежать выделения кучи в обычный случай).

Если вы используете компилятор Microsoft, MS имеет нестандартный _snprintf(), который имеет серьезные ограничения не всегда нулевого завершения буфера и не указывает, насколько большой буфер должен быть.

Чтобы обойти поддержку Microsoft без поддержки, я использую почти общедоступный домен snprintf() от Holger Weiss.

Конечно, если ваш компилятор, отличный от MS C или С++, отсутствует snprintf(), код из приведенной выше ссылки также должен работать.

Ответ 3

Я бы использовал двухэтапный подход. Как правило, большой процент строк вывода будет находиться под определенным порогом, и лишь немногие будут больше.

Этап 1, используйте статический буфер разумного размера, такой как 4K. Поскольку snprintf() может ограничить количество написанных символов, вы не получите переполнение буфера. То, что вы получите от snprintf(), - это количество символов, которое было бы написано, если ваш буфер был достаточно большим.

Если ваш вызов snprintf() возвращает менее 4K, используйте буфер и выйдите. Как было сказано, подавляющее большинство звонков должно просто сделать это.

Некоторые не будут и что, когда вы войдете на этап 2. Если вызов snprintf() не будет помещаться в буфер 4K, вы, по крайней мере, теперь знаете, какой большой буфер вам нужен.

Выделите с помощью malloc() буфер, достаточно большой, чтобы удерживать его, затем snprintf() снова в этот новый буфер. Когда вы закончите с буфером, освободите его.

Мы работали над системой за дни до snprintf(), и мы получили тот же результат, связав файл с файлом, связанным с /dev/null, и с помощью fprintf() с этим. /dev/null всегда гарантировало, что вы получите столько данных, сколько вы его дадите, чтобы мы могли получить размер от этого, а затем при необходимости выделим буфер.

Сохраняйте вид, что не все системы имеют snprintf() (например, я понимаю его _snprintf() в Microsoft C), поэтому вам, возможно, придется найти функцию, выполняющую одно и то же задание, или вернуться к решению fprintf /dev/null.

Также будьте осторожны, если данные могут быть изменены между проверкой размера snprintf() и фактическим snprintf() на буфер (т.е. wathch out для потоков). Если размеры увеличатся, вы получите повреждение переполнения буфера.

Если вы следуете правилу, что данные, переданные функции, принадлежат этой функции исключительно до тех пор, пока не будут возвращены, это не будет проблемой.

Ответ 4

Для того, что стоит, asprintf - это расширение GNU, которое управляет этой функциональностью. Он принимает указатель как выходной аргумент, а также строку формата и переменное количество аргументов и записывает обратно указателю адрес правильно распределенного буфера, содержащего результат.

Вы можете использовать его так:

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char *hi = "hello"; // these could be really long
    char *everyone = "world";
    char *message;
    asprintf(&message, "%s %s", hi, everyone);
    puts(message);
    free(message);
    return 0;
}

Надеюсь, это поможет кому-то!

Ответ 5

Я искал ту же функциональность, о которой вы говорите, но, насколько мне известно, в С++ не так просто, как метод C99, потому что С++ в настоящее время не включает функции, добавленные в C99 (такие как snprintf).

Лучше всего использовать объект stringstream. Это немного более громоздко, чем четко написанный вызов sprintf, но он будет работать.

Ответ 6

Взгляните на CodeProject: CString-clone Использование стандартного С++. Он использует предложенное вами решение с увеличенным размером буфера.


// -------------------------------------------------------------------------
    // FUNCTION:  FormatV
    //      void FormatV(PCSTR szFormat, va_list, argList);
    //
// DESCRIPTION: // This function formats the string with sprintf style format-specs. // It makes a general guess at required buffer size and then tries // successively larger buffers until it finds one big enough or a // threshold (MAX_FMT_TRIES) is exceeded. // // PARAMETERS: // szFormat - a PCSTR holding the format of the output // argList - a Microsoft specific va_list for variable argument lists // // RETURN VALUE: // -------------------------------------------------------------------------
void FormatV(const CT* szFormat, va_list argList)
{
#ifdef SS_ANSI

    int nLen    = sslen(szFormat) + STD_BUF_SIZE;
    ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList);
    ReleaseBuffer();
#else
    CT* pBuf            = NULL;
    int nChars          = 1;
    int nUsed           = 0;
    size_type nActual   = 0;
    int nTry            = 0;

    do  
    {
        // Grow more than linearly (e.g. 512, 1536, 3072, etc)

        nChars          += ((nTry+1) * FMT_BLOCK_SIZE);
        pBuf            = reinterpret_cast<CT*>(_alloca(sizeof(CT)*nChars));
        nUsed           = ssnprintf(pBuf, nChars-1, szFormat, argList);

        // Ensure proper NULL termination.
        nActual         = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1);
        pBuf[nActual+1]= '\0';


    } while ( nUsed < 0 && nTry++ < MAX_FMT_TRIES );

    // assign whatever we managed to format

    this->assign(pBuf, nActual);
#endif
}

Ответ 7

Поскольку вы используете С++, нет необходимости использовать какую-либо версию sprintf. Самый простой способ - использовать std:: ostringstream.

std::ostringstream oss;
oss << a << " " << b << std::endl;

oss.str() возвращает std::string с содержимым того, что вы написали в oss. Используйте oss.str().c_str(), чтобы получить const char *. В конечном итоге будет намного легче справляться и устранить утечки памяти или переполнение буфера. Как правило, если вы беспокоитесь о проблемах с памятью, подобных этому на С++, вы не используете весь этот язык в полной мере, и вы должны переосмыслить свой дизайн.