Анимация
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

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 в шаблонах. Рассмотрим следующий код.

tempi atе<typename Т>

void PrettyFormatC т value, char* buf ) {

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

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

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

tempi ate<tyреname Т>

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

не определен

templateo void PrettyFormat<int>Cint value, char* buf){ sprintfC buf, "%d , value );

templateo void PrettyFormat<char>Cchar value, char* buf){ spri ntfС buf, "%c , value );

... и т.д. ...

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

sprintf

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

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

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

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

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

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

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

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



Задача 3. Строчный двор.

Часть 2: стандартные альтернативы Сложность: 6

Продолжаем сравнительный анализ snprintf. std:: stringstream, std:: str stream и нестандартного, но элегантного boost:: lexical cast.

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

1. Проведите сравнительный анализ каждой из перечисленных альтернатив функции sprintf и выявите их сильные и слабые стороны, используя решение задачи 2 в качестве образца;

a) snprintf

b) std::stringstream

в) std::strstream

г) boost::lexical cast

Решение

Альтернатива №1: snprintf

1. Проведите сравнительный анализ каждой из перечисленных альтернатив функции sprintf и выявите их сильные и слабые стороны, используя решение задачи 2 в качестве образца:

а) snprintf

Среди прочих вариантов, snpri ntf, естественно, ближе других к spri ntf. В этой функции добавлена только одна, но очень важная возможность: указать максимальный раз.мер выходного буфера, тем самым обеспечивая невозможность переполнения буфера. Если переданный буфер слишком mbjt, выходная строка будет усечена.

Функция snpri ntf долгое время была широко распространенным иестаьщартным расширением практически во всех реализациях компиляторов С. После принятия стандарта С99 [С99] функция snpri ntf официально стала частью стандарта. Пока ваш компилятор не станет полностью С99-совместимым, возможно, вам придется использовать некоторое другое имя функции, специфичное для вашего компилятора (например, в ряде компиляторов это snprintf).

Честно говоря, всегда следует использовать snpri ntf вместо spri ntf (и следовало даже до того, как snpri ntf стал стандартом). Использование функций без проверки длины буфера запрещено в большинстве приличных стандартов кодирования, и не напрасно. Использование sprintf приводит к массе проблем, от аварийного завершения в общем случае до проблем безопасности в частности*.

Используя snpri ntf, мы можем написать корректный код безопасной функции PrettyFormat, что мы уже пытались, но не смогли сделать ранее.

Это реальная проблема, причем характерная не только для функции spri ntf (), но и для других функций без проверки длины буфера. Попробуйте выполнить поиск в Google по ключевым словам "strcpy" и "buffer overflow", и убедитесь в том, что это действительно так.

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



пример 3-1: преобразование данных в строку с использованием функции snprintf.

void PrettyFormatC int i, char* Этот код столь же прост, более безопасен: snprintfС buf, buflen, "%4d

buf, int buflen ) { как и ранее, но уже

i );

Заметим, что при этом все равно остается возможность для ошибки - вызываю-щая функция может передать неверный размер буфера. Это означает, что 100% безопасности в плане переполнения буфера не обеспечивает и snprintf, ио она гораздо безопаснее функции sprintf. На вопрос "Безопасна ли эта функция в смысле переполнения буфера?" следует однозначно ответить "Да".

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

преобразование данных в строку с использованием функции snpri ntf, которая не полностью отвечает стандарту С99.

void PrettyFormatC int i, char* buf, i nt buflen ) { Этот код столь же прост, как и ранее, но уже более безопасен:

ifС buflen > О ) {

snprintfC buf, buflen-l, buf[buflen-l] = \0;

"%4d", i );

Bo всех остальных отношениях функции sprintf и snpri ntf идентичны. Приведем следующую таблицу сравнения snpri ntf и spri ntf.

snprintf

sprintf

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

Да: только в \С99], но, вероятно, будет и в

С+-ьОх

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

ясность?

Эффективность, без излишних

распределений памяти?

Безопасность в плане

переполнения буфера?

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

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

Да: [С90], 1c-F+031, [С99]

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

> рекомендация

Никогда не используйте функцию spri ntf.

Если вы решите использовать возможности стандартного ввода-вывода из С, всегда используйте только тс функции, которые проверяют размер буфера, такие как



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