|
Программирование >> Обобщенные обратные вызовы
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 может привести к переполнению буфера 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 совпадают. Такая ошибка может остаться незамеченной до тех пор, пока вы не перенесете вашу программу на новую платформу, где размер int не равен размеру long int, но даже тогда такая ошибка может не приводить к некорректному выводу или аварийному завершению программы, И наконец, еще одна неприятность. 5. Невозможность работы в шаблонах. Очень трудно использовать spri ntf в шаблонах. Рассмотрим следующий код, tempiate<typename Т> void PrettyFormatC т value, char* buf ) { spri ntf( buf, %/* что должно быть здесь? */ , value ); Лучшее (или худшее?), что можно сделать в этой ситуации, - это объявить основной шаблон и обеспечить специализации для всех типов, совместимых со sprintf. Плохо: попытка шаблонизации 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. собрав информацию о ней в следующей таблице.
Решения, которые будут рассмотрены в следующей задаче, обеспечивают другие компромиссы между указанными параметрами. Задача 3. Строчный двор. Часть 2: стандартные альтернативы Сложность: 6 Продолжаем сравнительный анализ snprintf. std::stringstream, std::strstream и нестандартного, но элегантного 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. В этой функции добавлена только одна, но очень важная возможность: указать максимальный раз.мер выходного буфера, тем самым обеспечивая невозможность переполнения буфера. Если переданный буфер слишком Maji, выходная строка будет усечена. Функция 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-браузеру или серверу, код которого содержит вызовы функций без проверки размера буфера, может привести к перезаписи данных в памяти за внутренним буфером. В ряде случаев это позволяет разработать такой злонамеренный запрос, содержащий код, который в результате будет выполнен профаммой. Просто удивительно, какое количество профамм разработаны таким образом, с использованием вызовов без проверки длины!
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |