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

Следить за возникновением исключений можно стандартными средствами С или C++. Операторы __try и __except, поддерживаемые С, очень просты в использовании, однако они плохо согласуются с конструкциями C++. В C++ удобнее использовать стандартные ключевые слова try и catch.

Существуют некоторые особенности использования этих операторов. Дело в том, что по умолчанию стандартные исключения Windows не передаются в обработчик исключений, использующий catch. Чтобы решить проблему, необходимо при помощи функции _set_se_translator определить вашу собственную функцию, которая осуществляет преобразование исключений в стиле С (именно такие исключения генерирует Windows) в исключения в стиле C++. Это может быть очень простая подпрограмма, которая просто-напросто генерирует код исключения, являющийся целым числом.

Как только вы обнаружили возникновение исключения, вы можете выделить соответствующую страницу памяти, вернуть значение элемента массива по умолчанию или выполнить любую другую операцию, которая уместна в данном случае. При обращении к зарезервированной, но не выделенной странице возникает исключение 0х000000З. Рассмотрим код в листинге 3.

Листинг 3. Бесконечный буфер

#include <windows.h>

#include <stdio.h>

#include <eh.h>  // преобразование исключений C в исключения C++

static int maxindex;

char *getbuffer()

{

SYSTEM_INFO info;

// определение размера страницы

GetSystemInfo(&info);

char *rv;

rv=(char *)VirtualAlloc(NULL, maxindex=info.dwPageSize*1010,

MEM_RESERVE,PAGE_READWRITE);

return rv;

}

void cppexcept( unsigned int u, _EXCEPTION_POINTERS* pExp )

{

throw u;

}

void putbuffer(char *p,int index, char value)

{

_se_translator_function oldhandler=_set_se_translator(cppexcept);

try {

p[index]=value;

}

catch (unsigned int code)

{

printf("Code=%x @%d\n",code,index);

if (code!=0xC0000005)

{

printf("Неизвестное исключение\n");

exit(1);

}

// проверка: находится ли индекс в нужном диапазоне

if (index>=maxindex)

{

printf("Попытка доступа к ячейку за границами буфера\n");

exit(2);

}

// Выделяем один байт

// на самом деле система выделяет целую страницу

VirtualAlloc(p+index, 1, MEM_COMMIT, PAGE_READWRITE);

_set_se_translator(oldhandler);

// еще одна попытка записать в буфер

putbuffer(p,index,value);

}

_set_se_translator(oldhandler);

}

void freebuffer(char *p)

{

VirtualFree(p,0,MEM_RELEASE);

}

//Основная программа (не может располагаться отдельно от библиотеки)

void main()

{

char *p=getbuffer();

for (int i=0;i<4000000;i++)

{

putbuffer(p,i,i&0xFF);

}

freebuffer(p);

printf("Pass #2\n");

p=getbuffer();

// now backwards for variety

for (int j=4000000-1;j>=0;j--)

{

putbuffer(p,j,j&0xFF);

}

freebuffer(p);

}

Это исходный код простой библиотеки, которая обеспечивает для основной программы простые средства работы с буфером. Программа построена для обработки системных исключений при компилировании в Visual C++. Опять же для простоты все функции библиотеки располагаются в том же файле, что и основная программа. Функция getbuffer резервирует 1010 страниц памяти, но не выделяет их (на машинах архитектуры Intel такое количество страниц соответствует приблизительно 4 Мбайт памяти). Таким образом, ни одной страницы физической или виртуальной памяти не расходуется. Вместо этого лишь резервируется адресное пространство.

Когда основная программа хочет записать в буфер байт данных, она обращается к функции putbuffer. Эта функция чрезвычайно проста. Основное действие выполняется в строке, в которой элементу буфера с индексом Index присваивается значение value. Если по указанному адресу существует выделенная память, то эта строчка является единственной, которая выполняется в рамках данной функции.