2010-10-13 3 views
36

При преобразовании Int так:Определение размера буфера sprintf - что такое стандарт?

char a[256]; 
sprintf(a, "%d", 132); 

, что это лучший способ, чтобы определить, насколько большой должно быть? Я предполагаю, что установка вручную прекрасна (как я видел, она используется повсюду), но насколько она велика? Какова максимальная ценность int в 32-битной системе, и есть ли какой-то сложный способ определения этого на лету?

+0

Конечно, если вы используете C++, вместо этого вы можете просто использовать std :: string и std :: stringstream для достижения того, чего вы хотите, даже не думая о требованиях к памяти. Но это действительно зависит. Я знаю, что вопрос для C, но, возможно, это может быть полезно в любом случае. –

+6

@Robert: если вместо этого используется Python, вы можете использовать 'str'; -p –

+0

@Steve Я вижу вашу точку зрения, просто хотел посмотреть, просто ли плакат только что прыгнул на C, когда им это не нужно. –

ответ

11

Максимально возможное число бит в ИНТЕ CHAR_BIT * sizeof(int) и десятичная цифра «стоит» по меньшей мере, 3 бит, поэтому свободной верхней границы пространства, необходимого для произвольного int является (CHAR_BIT * sizeof(int)/3) + 3. Это +3 является тем, что мы округлили при делении, один для знака, один для терминатора nul.

Если «по 32-разрядной системе» вы имеете в виду, что знаете, что int - 32 бита, тогда вам нужно 12 байт. 10 для цифр, один для знака, один для терминатора nul.

В вашем конкретном случае, где преобразованный int составляет 132, вам нужно 4 байта. Бадум, тиш.

Если буферы фиксированного размера могут использоваться с разумной привязкой, они являются более простым вариантом. Я не очень-то смиренно заявляю, что приведенная выше оценка разумна (13 байт вместо 12 для 32 бит int и 23 байта вместо 21 для 64 бит int). Но для сложных случаев в C99 вы можете просто позвонить snprintf, чтобы получить размер, а затем malloc так много. Это слишком много для такого простого случая.

+2

Использование 'malloc' для этого смешно. Это сдерживает ваш код, добавляя случай сбоя, который вы должны проверить - и что вы будете делать, если это не удастся?!? Просто используйте буфер с правильным размером, как вы объяснили, как это сделать. –

+0

@R. Очень хорошо, я расширю свое существующее, «немного перехитрить для такого простого случая, как это». –

+2

@R: Использование malloc не смешно, если человек, написавший код, заинтересован в обучении или знает основы самого языка. Говорить людям просто использовать std :: string, потому что все тяжелые работы уже сделаны, не знают об этом; то есть желая знать, как все работает под капотом, так сказать.Может быть, оригинальный плакат хочет знать, как std :: string делает то, что он делает? – Eric

0

Прежде всего, sprintf - это дьявол. Если что-нибудь, используйте snprintf, иначе вы рискуете уничтожить память и свернуть приложение.

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

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

+0

Спасибо. snprintf это тогда. –

+8

Нет опасности использовать 'sprintf' с буфером с правильным размером. Для числового вывода упрощается калибровка буфера. См. Ответ Стива. –

+0

@R .. Предполагая, что другая нить не сбивает некоторые данные за вашей спиной ... –

1

Его хорошо, что вы беспокоитесь о размере буфера. Чтобы применить эту мысль в коде, я использовал бы snprintf

snprintf(a, 256, "%d", 132); 

или

snprintf(a, sizeof(a), "%d", 132); // when a is array, not pointer 
+1

'snprintf' решает только половину проблемы. Если вы не уверены, что ваш буфер достаточно велик, вам нужно использовать 'snprintf' для проверки необходимого размера или убедиться, что ваш код не имеет ошибок при выходе из усечения. Ответ Стива намного лучше. –

65

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

int size = snprintf(NULL, 0, "%d", 132); 
char * a = malloc(size + 1); 
sprintf(a, "%d", 132); 

Я сломаю то, что здесь происходит.

  1. В первой строке мы хотим определить, сколько символов нам нужно.Первые 2 аргумента snprintf говорят, что я хочу написать 0 символов результата NULL. Когда мы это сделаем, snprintf на самом деле не будет писать ни одного символа в любом месте, он просто вернет количество символов, которые были бы написаны. Это то, что мы хотели.
  2. Во второй строке мы динамически выделяем память на указатель char. Удостоверьтесь и добавьте 1 к требуемому размеру (для завершающего символа \0).
  3. Теперь, когда имеется достаточно памяти, выделенной для указателя char, мы можем безопасно использовать sprintf для записи целого числа в указатель char.

Конечно, вы можете сделать его более кратким, если хотите.

char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1); 
sprintf(a, "%d", 132); 

Если это не программа «быстрая и грязная», вы всегда хотите, чтобы убедиться, чтобы освободить память, вы с названием malloc. Здесь динамический подход усложняется с C. Однако IMHO, если вы не хотите выделять огромные указатели char, когда большую часть времени вы будете использовать только небольшую часть из них, то я не думаю это плохой подход.

+0

Собственно, вы часто можете просто использовать 'alloca' вместо' malloc'. И если полученный код все еще слишком раздутый, сделайте для него макрос. – thejh

+4

Насколько переносимым является 'alloca'? Это, конечно же, не ANSI C. –

+3

Несомненно, для альтернативы alloca для портативного C99 просто использовать массив переменной длины? 'int size = ...; char a [размер + 1]; sprintf (... '? – Tommy

7

Я вижу, что этот разговор пару лет, но я нашел его, пытаясь найти ответ для MS VC++, где snprintf не может использоваться для поиска размера. Я отправлю ответ, который я наконец нашел, если он поможет кому-либо еще:

VC++ имеет функцию _scprintf специально для поиска необходимого количества символов.

2

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

char *text; 
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2); 

log10 (значение) возвращает количество цифр (минус один), необходимых для сохранения положительного ненулевого значения в базе 10. Она идет немного с рельсов для чисел меньше единицы, поэтому мы указываем abs() и специальную логику кода для нуля (тернарный оператор, test? truecase: falsecase). Добавьте один для пробела, чтобы сохранить знак отрицательного числа (val < 0), один из которых составляет разницу между log10 и еще один для нулевого терминатора (всего 2), и вы только что вычислили точный объем пространства для хранения, требуемый для данного номера, без вызова snprintf() или эквивалентов дважды, чтобы выполнить эту работу. Плюс он использует меньше памяти, как правило, чем требуется INT_MAX.

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

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

+0

Скажем, что ваше целое - это 'INT_MIN'. Тогда' abs (val) 'is' INT_MIN', 'log10', примененный к нему, возвращает NaN, а преобразование NaN в' int' - неопределенное поведение. Кроме того, если вы печатают 64-битные целые числа, самые большие из них не представляются точно так же, как 'double'. –

+0

@Pascal Cuoq - Интересная причуда, спасибо за это. Я думаю, что решение на самом деле не закончено без проверки, пытаемся ли мы распределите хранилище для строкового представления INT_MIN, но я чувствую себя ленивым, поэтому я оставлю это как упражнение для всех, кто на самом деле это делает. - PS «неопределенное поведение» - это преуменьшение. В GCC, printf ("% d \ n", (int) log10 ((double) abs (-2147483647 - 1))); выплевывает «-2147483648» вместо ожидаемого «9» - как странно. –

10

Возможно, решение Daniel Standage работает для любого количества аргументов, используя vsnprintf, который находится на C++ 11/C99.

int bufferSize(const char* format, ...) { 
    va_list args; 
    va_start(args, format); 
    int result = vsnprintf(NULL, 0, format, args); 
    va_end(args); 
    return result + 1; // safe byte for \0 
} 

Как указано в c99 standard, раздел 7.19.6.12:

Функция vsnprintf возвращает количество символов, которые бы были написаны уже н было достаточно большим, не считая завершающий нулевой символ, или отрицательное значение, если произошла ошибка
кодирования.

+0

Это чистый C99 to. – fshp

+0

Спасибо. Даже не заметил. Обновлен мой ответ. –

+0

'_vscprintf' - лучшее решение. Посмотрите - https://stackoverflow.com/a/9369242/987850 – 23W