Управление памятью в операционной системе WINDOWS, страница 6

}

return msg;      

}

void main()

{

char *m=getmsg();

try {

printf("Вся строка: %s\n",m);

m=strtok(m," \t");

printf("token1=%s\n",m);

m=getmsg();

printf("Вся строка: %s\n",m);

}

catch (...)  // следить за любыми исключениями

{

printf("Произошло исключение!\n");

}

getchar();  // ожидание для отладочных целей

}

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

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

Код, приведенный в листинге 2, следит за возникновением исключений (он реагирует на любое возникающее исключение), таким образом, в случае некорректного обращения к защищенной странице программа сможет должным образом отреагировать на это. В нашем случае на экран выводится сообщение об ошибке, текст которого определяется программистом. Если убрать из текста программы оператор try, система прервет работу программы и выведет на экран собственное системное сообщение об ошибке.

Что мешает основной программе обратиться к VirtualProtect? Вообще-то ничего. Однако обращение к этому вызову — более продуманное и взвешенное действие, чем простая передача указателя из функции в функцию. Код, использующий VirtualProtect, несомненно, обладает большей степенью надежности. Обратиться к этому вызову по ошибке фактически невозможно, ведь для этого необходимо  знать начальный адрес защищенной области памяти, который далеко не всегда совпадает с переданным вам указателем (как это случилось в нашем случае).

Способы работы с разреженной памятью

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

Используя этот механизм, можно зарезервировать достаточно большой участок памяти, а затем, по мере заполнения его данными, выделять дополнительную память из этого участка последовательно страница за страницей в автоматическом режиме. При этом программа будет записывать данные в зарезервированную область в точности так же, как если бы вся эта память была бы уже выделена. При попытке записи в зарезервированную, но не выделенную страницу происходит исключение. Можно написать специальный обработчик этого исключения, который будет выделять страницу, в которую основная программа пытается записать данные. Таким образом, основная программа и не подозревает о том, что память только зарезервирована, но не выделена. Выделением памяти занимается обработчик исключения.

Еще одна область применения этих механизмов — реализация эффективных разреженных таблиц. Представьте, что вы работаете с массивом, в котором фактически все элементы равны одному и тому же значению и лишь некоторые из них отличаются от этого значения. Такие массивы часто используются, например, в инженерных вычислениях. Вы можете разбить массив на страницы по 4 Кбайт и хранить в памяти только те страницы, содержимое которых отличается от общего значения. Например, предположим, что для хранения массива требуется выделить 100 страниц, 96 из них содержит нули, а в четырех оставшихся страницах значения некоторых ячеек памяти отличаются от нуля. Нет смысла тратить память на хранение нулей. Все нулевые страницы помечаются как страницы с запрещенным доступом или остаются зарезервированными, но не выделенными. Когда программа, осуществляющая вычисления, обращается к такой странице, Windows генерирует исключение. Программа перехватывает это исключение и делает вывод, что запрашиваемое значение равно нулю.