
Динамо-машины Метод Сократа
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 |