Доверяй, но проверяй Косякин Антон Спецвыпуск Xakep, номер #045, стр. 045-024-1 (deil@gameland.ru) Учимся грамотной работе с памятью Ущерб от ошибок и недосмотров программистов при работе с памятью огромен. Взять хотя бы всем известный MS Windows (R): в последнее время большая часть патчей к нему связана как раз с устранением последствий кривой работы с памятью и переполнения буфера. Давай попробуем во всем разобраться, чтобы в будущем этого избежать. Неправильный доступ и переполнения Рассмотрим для примера функцию, добавляющую одну строку в конец другой, strcat(s1, s2). Допустим, в начале программы мы сделали так: char *s1 = malloc(2); memset(s1, 0, 2), выделив строке s1 два байта и обнулив их. Теперь, если, например, strcat(s1, "j00 r 0wn3d"), функция честно скопирует строку "j00 r 0wn3d" по адресу, на который указывает s1. Ведь strcat не знает о том, что наша программа выделила всего два байта под s1! Поэтому она просто перезапишет чужие данные, заполнив их куском нашей строки. В данном случае это не очень критично. Однако вот такой код уже может прилично навредить: Листинг Нарочно делаем себе гадости typedef struct _test { char s1[2]; char s2[10]; } test_t; test_t t; strcpy(t.s2, "hello world"); strcpy(t.s1, "j00 r 0wn3d"); Отличие данного варианта от описанного выше в том, что в здесь s2 будет указывать на область, следующую сразу же за s1. И поэтому strcat(s1, "j00 r 0wn3d") с радостью перезапишет строку, хранящуюся в s2, даже не заметив этого :-). В таком случае опасность не особо велика и заключается только в том, что пользовательские данные могут быть утеряны и программа просто начнет себя странно вести. Но могут произойти и более занятные вещи. Рассмотрим функцию strlen, которая подсчитывает количество символов в строке. Предположим, что она перебирает символы до тех пор, пока не встретит ноль – он будет означать, что строка закончилась. Также будем считать, что у нас есть указатель на строку s1, для которой мы выделили сто байт. Давай забудем проинициализировать выделенную память нулями. Затем сделаем strcpy(s1, "hello world") и посчитаем длину нашей строки: strlen(s1) честно пробежится по словосочетанию “hello world” и пойдет считать дальше, пока не встретит ноль. В лучшем случае программа выдаст неправильное значение и все дальнейшие расчеты будут, мягко говоря, неточны :-). А в худшем? Наша бравая функция смело пробежит выделенные строке 100 б и пошлепает дальше, где наверняка в один прекрасный момент попытается прочитать байт, на чтение которого она прав не имеет. Любая система тотчас неодобрительно отреагирует на подобную наглость, а UNIX-система сразу же пошлет процессу сигнал SIGSEGV и грохнет его. В результате чего пользователь увидит заветное "Segmentation fault". Теперь обсудим такой пример: float *pi; *pi = 3.141592654; printf("pi is %f", *pi); Догадываешься, что может тут случиться? В начале программы создастся указатель на вещественное число, но мы его нарочно не проинициализировали. Выполнение такого кода в большинстве случаев приведет к "Segmentation fault" или появлению окошечка с ошибкой. Резюмируем все вышесказанное. Большинство проблем возникает из-за того, что программа пытается получить доступ к памяти, лежащей вне доступных ей адресов. Такие попытки операционная система пресекает в корне, прибивая процесс. |