Есть ли способ определить, сколько символов будет написано sprintf?
Я работаю на С++.
Я хочу написать потенциально очень длинную форматированную строку, используя sprintf (в частности, надежную подсчитанную версию, такую как _snprintf_s, но идея такая же). Пример приблизительной длины неизвестен во время компиляции, поэтому мне придется использовать некоторую динамически распределенную память, а не полагаться на большой статический буфер. Есть ли способ определить, сколько символов потребуется для конкретного вызова sprintf, поэтому я всегда могу быть уверен, что у меня есть достаточно большой буфер?
Мой отказ - я просто возьму длину строки формата, удвою ее и попробую. Если он работает, отлично, если нет, я просто удвою размер буфера и повторю попытку. Повторяйте, пока он не подходит. Не совсем лучшее решение.
Похоже, C99 поддерживает передачу NULL в snprintf для получения длины. Полагаю, я мог бы создать модуль, чтобы обернуть эту функциональность, если ничего другого, но я не сумасшедший об этой идее.
Может быть, fprintf для "/dev/null" / "nul" может работать? Любые другие идеи?
EDIT: Альтернативно, есть ли способ "обрезать" sprintf, чтобы он забирал среднюю запись? Если это возможно, он может заполнить буфер, обработать его, а затем начать заполнять с того места, где он остановился.
Ответы
Ответ 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 *
. В конечном итоге будет намного легче справляться и устранить утечки памяти или переполнение буфера. Как правило, если вы беспокоитесь о проблемах с памятью, подобных этому на С++, вы не используете весь этот язык в полной мере, и вы должны переосмыслить свой дизайн.