Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 [ 4 ] 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Задача 2. Строчный двор. Часть 1: sprintf Сложность: 3

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

Вопрос для новичка

1. Что такое sprintf? Перечислите как можно больше стандартных альтернатив spri ntf.

Вопрос для профессионала

2. В чем сильные и слабые стороны spri ntf? Будьте конкретны в своем ответе.

Решение

"Все животные равны, но некоторые животные равнее других". - Джордж Оруэлл, Скотный двор

I. Что такое spri ntf? Перечисли-ге как можно больше ста1здартных альтернатив spri ntf.

Рассмотрим следующий код С, который использует sprintf для преобразования целого числа в удобочитаемое строковое представление.

Пример 2-1: Строковое представление данных в С с использованием spri ntf. Функция PrettyFormat получает в качестве параметра целое число и преобразует его в строку в предоставленный буфер. Результат должен иметь размер не менее 4 символов.

void PrettyFormatC int i, char* buf ) { код, простой и понятный: sprintfС buf, "%4d", i );

Вопрос на засыпку: как сделать то же на С++?

Впрочем, это не совсем корректный вопрос, так как, в конце концов, этот же код вполне корректен и в С++. Вопрос на засыпку надо сформулировать так: если отбросить все офаничения стандарта С (если это и в самом деле Офзничения), то имеется ли лучший способ выполнить эти же действия в С++ с его классами, шаблонами и прочим?

Этот вопрос интересен тем, что пример 2-1 можно выполнить по крайней мере четырьмя разными стандартными способами, каждый из которых представляет собой определенный компромисс между ясностью, безопасностью типов, безопасьюстью времени исполнения и эффективностью. Кроме того, перефразируя свиней-ревизионистов из произведения Оруэлла, "все четыре способа стандартны, но некоторые из них стандартнее других", да и взяты они из разных стандартов. Они приведены ниже в том порядке, в котором мы будем их рассматривать.

1. sprintf [С99, С++03]

2. snprintf [С99]

3. std: :stringstream [С++03]

4. std: :strstream fC++03]

Перевод с англ. Д. Иванова, В. Недошивина (Дж. Оруэлл. Скотный двор. - Пермь, Изд. "КАПИК", [992.)



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

5. boost:: 1 exi cal......cast [Boost]

Итак, приступим к рассмотрению.

РадосI и и печали sprintf

2. В чем сильные и слабые стороны sprintf? Будьте конкретны в своем ответе.

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

Давайте проанализируем функцию sprintf более детально. У нее есть два наиболее важных достоинства и три недостатка.

/. Простота использования и ясность. Как только вы изучите, как флаги форматирования и их комбинации влияют на форматирование строки, использование spri ntf становится простым и очевидным. Очень трудно превзойти семейство функций pri ntf в задачах по форматированию текста. (Да, запомнить все флаги не просто, но обычно это относится к достаточно редко используемым флагам форматирования.)

2. Максимальная эффективность (возможность непосредственного использования существующих буферов). При использовании функции sprintf результат помешается непосредственно в заранее подготовленный буфер, так что функция PrettyFormat выполняет свою работу без динамического выделения памяти или других побочных действий. Она получает уже выделенную память и записывает результирующую строку непосредственно в эту память.

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

потом........ если в этом появится необходимость. В нашем случае необходимо учесть, что

эффективность достигается за счет инкапсуляции управления памятью.

Увы, как известно большинству пользователей функции sprintf, у нее есть и значительные недостатки.

3. Безопасность. Функция spri ntf - распространенный источник ошибок, связанных с переполнением буфера, когда размера выделенного буфера не хватает для размещения выводимой строки. Рассмотрим, например, следующий код.

char smal1Buf[5]; i nt value = 42;

PrettyFormat( value, smallBuf ); вроде бы все в порядке assertC value ==42 );

В этом случае значение 42 достаточно мало для того, чтобы результат "42\0" полностью разместился в пяти байтах smal 1 Buf. Но представим, что однажды код изменится на такой, как показано ниже.

char smal1Buf[5]; int value = 12108642 ;

В настоящее время - [C99], стандарт С++ [С++031 основан на более ранней версии С. Распространенная ошибка начинающих программистов - полагаться на спецификатор ширины вывода, в нашем случае - 4, который не работает так, как они рассчитывают, поскольку этот спецификатор ука.зывает минимальную длину вывода, а не максимальную.



PrettyFormatC value, smallBuf ); ax!

assertC value == 12108642 ); У нас проблема!

При этом начинается запись в память за пределами small Buf, что может привести к записи в память, выделенную переменной val ue, если компилятор расположит ее в памяти непосредственно за переменной small Buf,

Сделать код из примера 2-1 существенно безопаснее - достаточно трудная задача. Можно изменить код так, чтобы в функцию передавался размер буфера и проверялось значение, которое возвращается функцией spri ntf и показывает, сколько байтов записано функцией. Это даст нам код, который выглядит примерно следующим образом.

плохое решение

void PrettyFormatC int i, char* buf, int buflen ) {

if Cbuflen <= sprintfCbuf,"%4d",i)) { Лучше не стало Ну и что? Пока мы выясним, что произошла неприятность, эта неприятность уже успеет испортить память. Мы просто убеждаемся, что неприятность действительно произошла.

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

4. Безопасность типов. При использовании функции sprintf ошибки в применении типов яаляются не ошибками времени компиляции, а ошибками времени выполнения программы. Они вдвойне опасны тем, что могут остаться необнаруженными даже на этапе выполнения. Функции семейства printf используют переменные списки аргументов из С, а компиляторы С не проверяют типы параметров в таких списках*. Почти каждый программист на С хоть раз, но имел сомнительное удовольствие искать источник ошибки, вызванной неверным спецификатором формата, и очень часто такая ошибка обнаруживается только после ночи работы с отладчиком в попытках воспроизвести загадочное сообщение об ошибке, присланное пользователем.

Конечно, код в примере 2-1 тривиален, и ошибиться здесь сложно. Но кто застрахован от опечаток? Ваша рука скользнула чуть ниже, и вместо d вы ударили по клавише с, в результате чего получился следующий код.

sprintfС buf, "%4с", i );

Правда, в данном случае вы быстро обнаружите ошибку, когда на выходе вместо числа получите некоторый символ (так как в этом случае функция sprintf молча выведет младший байт i как символ). А можно промахнуться чуть левее и ударить по клавише s, получив следующий код.

Заметим, что в некоторых случаях проблему переполнения буфера можно разрешить, по крайней мере, теоретически, создавая собственные форматные строки в процессе работь(. Я говорю "теоретически", потому что обычно это весьма непрактично; такой код всегда невразуми-пелен и часто подвержен ошибкам. Вот вариант Б, Страуструпа, предложенный им в [.Stroustrup99] с оговоркой о том, что это профессиональное решение, ие рекомендуемое к использованию новичками,

char fmt[10];

создание строки формата: обычный %s может привести к переполнению буфера spri ntfCfmt,"%%%ds",max-1);

Считываем не более max-1 символов в переменную name scanf Cfrrt,name);

Использование соответствующего инструментария наподобие lint может помочь обнаружить ошибки такого рода,



0 1 2 3 [ 4 ] 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85