Динамо-машины  Метод Сократа 

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 86 87 88 89 90

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

5. boost:: 1 exical......cast [Boost]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

char smal1Buf[5]; int value = 12108642 ;

В настоящее время - [C99], стандарт С++ [С++03\ основан на более ранней версии С. Распространенная ошибка начинающих программистов - полагаться на спецификатор ширины вывода, в нашем случае - 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 (buflen lt;= sprintfCbuf, %4d ,i)) { Лучше не стало Ну и что? Пока мы выясним, что произошла неприятность, эта неприятность уже успеет испортить память. Мы просто убеждаемся, что неприятность действительно произошла.

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

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

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

sprintfС buf, %4с , i );

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

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

char fmt[10];

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

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

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



sprintf( buf, %4s , i );

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

Но вот более тонкая ошибка. Что произойдет, если мы ошибочно заменим d на Id?

sprintfC buf, %41d , i );

В этом случае строка формата говорит функции sprintf, что в качестве первой части форматируемых данных ожидается значение типа long int, а не просто int. Это тоже плохой С-код, причем проблема в том, что это не только не ошибка времени компиляции, но может не быть и ошибкой времени выполнения. На многих платформах результат работы программы от этого не изменится, поскольку на них размер int и размер long int совпадают. Такая ошибка может остаться незамеченной до тех пор, пока вы не перенесете вашу программу на новую платформу, где размер i nt не равен размеру long i nt, но даже тогда такая ошибка может не приводить к некорректному выводу или аварийному завершению программы,

И наконец, еще одна неприятность.

5. Невозможность работы в шаблонах. Очень трудно использовать spri ntf в шаблонах. Рассмотрим следующий код.

tempiate lt;typename Т gt;

void PrettyFOrmatC т value, char* buf ) {

spri ntf( buf, %/* что должно быть здесь? */ , value );

Лучшее (или худшее?), что можно сделать в этой ситуации, - это объявить основной шаблон и обеспечить специализации для всех типов, совместимых со spri ntf.

Плохо: попытка шаблонизации PrettyFormat.

tempi ate lt;tyреname Т gt;

void PrettyFOrmatC T value, char* buf ); Главный шаблон

не определен

templateo void PrettyFormat lt;int gt;Cint value, char* buf){ sprintfC buf, %d , value );

templateo void PrettyFormat lt;char gt;(char value, char* buf){ spri ntfС buf, %c , value );

... и т.д. ...

Подведем итог нашему обсуждению функции spri ntf. собрав информацию о ней в следующей таблице.

sprintf

Стандартность?

Да: 1С90], [С-ь-ьОЗ], [С99]

Простота использования и ясность?

Эффективность, без излишних распределений памяти?

Безопасность в плане переполнения буфера?

Безопасность типов?

Использование в шаблонах?

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



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 86 87 88 89 90