Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming_Windows_95_Part_II.pdf
Скачиваний:
41
Добавлен:
05.06.2014
Размер:
3.02 Mб
Скачать

14

Выделение памяти

Достаточно ли убедил вас совет использовать библиотечные функции C для выделения памяти? Для того чтобы подкрепить это утверждение, начнем с краткого обзора.

Библиотечные функции C

Вы можете определить в программе указатель (например, на массив целых чисел) следующим образом:

int *p;

Указатель p — 32-разрядное число, которое неинициализировано. Вы можете выделить блок памяти, на который будет указывать p, следующим образом:

p =(int *) malloc(1024);

При этом выделяется блок памяти размером 1024 байта, который может хранить 256 32-разрядных целых. Указатель, равный NULL, показывает, что выделение памяти не было успешным. Можно также выделить такой блок памяти, используя следующий вызов:

p =(int *) calloc(256, sizeof(int));

Два параметра функции calloc перемножаются и в результате получается 1024 байта. Кроме того, функция calloc производит обнуление блока памяти.

Если вам необходимо увеличить размер блока памяти (например, удвоить его), то вы можете вызвать функцию:

p =(int *) realloc(p, 2048);

Указатель является параметром функции, и указатель (возможно, отличающийся по значению от первого, особенно, если блок увеличивается) является возвращаемым значением функции. Этот пример показывает, что операционная система (в данном случае Windows 95) может перемещать блок в рамках виртуальной памяти. Например, если вы выделили блок размером 1024 байта, то его виртуальный адрес может быть равен 0x00750100. Вы можете выделить второй блок памяти и получить виртуальный адрес 0x00750504. Расширив первый блок памяти до 2048 байт, использовать тот же виртуальный адрес невозможно. В этом случае Windows 95 должна переместить блок в физической памяти на новую страницу.

При окончании работы с памятью, вызовите функцию:

free(p);

Только указанные четыре функции определены в стандарте ANSI языка C. Часто производители компиляторов реализуют несколько большее количество функций, наиболее распространенной из которых является функция _msize, возвращающая размер выделенного блока.

Фундаментальное выделение памяти в Windows 95

Как уже говорилось ранее, все, что вы можете делать с помощью библиотечных функций C, вы можете делать самостоятельно, или используя вызовы функций ядра Windows 95. Ниже приведена функция Windows 95 для выделения блока памяти для указателя на целые:

p =(int *) GlobalAlloc(uiFlags, dwSize);

Функция имеет два параметра: набор флагов и размер выделяемого блока в байтах. Она возвращает виртуальный адрес, который может использоваться в программе для доступа к выделенной памяти. Значение NULL говорит о том, что имеющейся в распоряжении памяти для выделения недостаточно.

За исключением одной, для каждой функции, начинающейся со слова Global, существует другая, начинающаяся со слова Local. Эти два набора функций в Windows 95 идентичны. Два различных слова сохранены для совместимости с предыдущими версиями Windows, где функции Global возвращали дальние указатели, а функции Local — ближние.

Хотя определения параметров немного отличаются, они оба являются 32-разрядными беззнаковыми целыми. Если первый параметр задать нулевым, то это эквивалентно использованию флага

GMEM_FIXED(равен нулю).

Такой вызов функции GlobalAlloc эквивалентен вызову функции malloc. В ранних версиях Windows присутствие флага GMEM_FIXED приводило к проблемам управления памятью, поскольку Windows не могла перемещать такие блоки в физической памяти. В Windows 95 флаг GMEM_FIXED вполне допустим, поскольку функция возвращает виртуальный адрес, и операционная система может перемещать блок в физической памяти, внося изменения в таблицу страниц.

15

Вы можете также использовать флаг:

GMEM_ZEROINIT

для обнуления всех байтов выделяемого блока памяти. Флаг GPTR включает в себя флаги GMEM_FIXED и GMEM_ZEROINIT, как определено в заголовочных файлах Windows:

#define GPTR(GMEM_FIXED | GMEM_ZEROINIT)

Имеется также функция изменения размера блока памяти:

p =(int *) GlobalReAlloc(p, dwSize, uiFlags);

Вы можете использовать флаг GMEM_ZEROINIT для обнуления добавляющихся в блок памяти байтов, если блок расширяется.

Существует функция, возвращающая размер блока памяти:

dwSize = GlobalSize(p);

и функция освобождения памяти:

GlobalFree(p);

Перемещаемая память

Как уже отмечалось ранее, в предыдущих версиях Windows наличие флага GMEM_FIXED приводило к проблемам при управлении памятью, поскольку Windows должна была сохранять блок памяти в фиксированном месте физической памяти. В Windows 95 блок памяти может быть перемещен в физической памяти при сохранении виртуального адреса неизменным. Функция GlobalAlloc, тем не менее, поддерживает флаг

GMEM_MOVEABLE

и комбинированный флаг для дополнительного обнуления блока памяти (как описано в заголовочных файлах

Windows):

#define GHND(GMEM_MOVEABLE | GMEM_ZEROINIT)

Флаг GMEM_MOVEABLE позволяет перемещать блок памяти в виртуальной памяти. Это необязательно означает, что блок памяти будет перемещен в физической памяти, но адрес, которым пользуется программа для чтения и записи, может измениться. Это звучит странно? Возможно. Но вскоре мы увидим, как работает этот механизм.

Вы можете задать вопрос: почему я могу захотеть использовать перемещаемую память, если я не обязан это делать? (И вы можете поставить его более четко после того, как увидите, что для этого требуется.) Ответ состоит в том, чтобы сохранить совместимость с существующим исходным кодом программ для Windows. Более удачный ответ — вы можете беспокоиться о фрагментации виртуальной памяти. Если ваша программа выделяет, расширяет, освобождает память, как сумасшедшая, то виртуальная память может сделаться фрагментированной. Может ли ваша программа дойти до предела в 2 ГБ виртуальной памяти до того, как будет достигнут предел 4, 8 или 16 МБ физической памяти? Это может произойти. Проблема встает острее при непрерывном использовании машин. (Как известно, после окончания рабочего дня запускаются программы-хранители экрана.) Программы, выполнение которых происходит в течение нескольких дней, в конце концов могут столкнуться с большей фрагментацией памяти, чем программы, разработанные для выполнения в течение одного-двух часов.

Поскольку это потенциальная проблема, то вы можете захотеть использовать перемещаемую память. Теперь рассмотрим, как это делается. Первым делом определим указатель и переменную типа GLOBALHANDLE:

int *p;

GLOBALHANDLE hGlobal;

Затем, выделим память, например так:

hGlobal = GlobalAlloc(GHND, 1024);

Обратите внимание, что не требуется никакого преобразования типа для возвращаемого функцией GlobalAlloc значения. Функция определена, как возвращающая значение типа GLOBALHANDLE, поскольку именно так и было в предыдущих версиях Windows.

Как и при использовании любого другого описателя Windows вам не нужно беспокоиться о его численном значении. Когда вам необходимо обратиться к памяти, для фиксации блока используйте вызов:

p =(int *) GlobalLock(hGlobal);

Эта функция преобразует описатель памяти в указатель. Пока блок зафиксирован, Windows не изменяет его виртуальный адрес. Когда вы заканчиваете работу с блоком, для снятия фиксации вызовите функцию:

GlobalUnlock(hGlobal);

16

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

Когда вы хотите освободить память, вызовите функцию GlobalFree с параметром-описателем, а не указателем. Если в данный момент вы не имеете доступа к описателю, то вам необходимо использовать функцию:

hGlobal = GlobalHandle(p);

Вы можете фиксировать блок памяти несколько раз до того, как снять с него фиксацию. Windows запоминает количество фиксаций, и каждое фиксирование требует снятия для того чтобы дать возможность блоку перемещаться. Перемещение блока в виртуальной памяти не есть перемещение байтов с одного места в другое — производятся только манипуляции с таблицами страниц. Единственной причиной для выделения перемещаемой памяти служит предотвращение фрагментации виртуальной памяти.

Удаляемая память

Если вы уже набрались смелости, чтобы использовать опцию GMEM_MOVEABLE, то, может быть, у вас хватит смелости попробовать использовать опцию:

GMEM_DISCARDABLE

Эта опция может быть использована только совместно с GMEM_MOVEABLE. Блок памяти, выделенный с этим флагом, может быть удален из физической памяти ядром Windows, когда необходима свободная память.

Может быть, это звучит кощунственно, но немного подумайте об этом. Например, блоки памяти, содержащие код, являются удаляемыми. Они являются защищенными от записи. Следовательно, быстрее загрузить код из исходного файла

.EXE, чем записывать его на диск, а затем вновь загружать с диска. Если вы выделяете память для неизменяемых данных, которые могут быть легко регенерированы (обычно загрузкой из файла), то можно сделать этот блок удаляемым. О том, что данные были сброшены, вы узнаете, когда вызовите функцию GlobalLock и получите в ответ NULL. Теперь, вы восстанавливаете данные.

Блок памяти не может быть удален до тех пор, пока счетчик фиксаций больше нуля. Для преднамеренного удаления блока памяти, вызовите:

GlobalDiscard(hGlobal);

Другие функции и флаги

Другим доступным для использования в функции GlobalAlloc является флаг GMEM_SHARE или GMEM_DDESHARE (они идентичны). Как следует из его имени, этот флаг предназначен для динамического обмена данными, который подробно рассматривается в главе 17.

Функции GlobalAlloc и GlobalReAlloc могут также включать флаги GMEM_NODISCARD и GMEM_NOCOMPACT. Эти флаги дают указание Windows не удалять и не перемещать блоки памяти для удовлетворения запросов памяти. Только излишне альтруистичные программисты используют эти флаги.

Функция GlobalReAlloc может также изменять флаги (например, преобразовывать фиксированный блок памяти в перемещаемый, и наоборот), если новый размер блока задан равным нулю, и указан флаг GMEM_MODIFY в параметре флагов.

Функция GlobalFlags возвращает комбинацию флагов GMEM_DISCARDABLE, GMEM_DISCARDED и GMEM_SHARE.

Наконец, вы можете вызвать функцию GlobalMemoryStatus (для этой функции нет функции-двойника со словом Local ) с указателем на структуру типа MEMORYSTATUS для определения количества физической и виртуальной памяти, доступной приложению.

На этом заканчивается обзор функций, начинающихся со слова Global. Windows 95 также поддерживает некоторые функции, которые вы реализуете сами или дублируете библиотечными функциями C. Это функции FreeMemory (заполнение конкретным байтом), ZeroMemory (обнуление памяти), CopyMemory и MoveMemory — обе копируют данные из одной области памяти в другую. Если эти области перекрываются, то функция CopyMemory может работать некорректно. Вместо нее используйте функцию MoveMemory.

Хорошо ли это?

Перед тем как осуществить доступ к памяти, вам, может быть, захочется проверить, возможен доступ или нет. Если указатель является недействительным, исключается общая защита программы. Предварительная проверка указателя гарантирует, что этого не произойдет. Функции IsBadCodePtr, IsBadReadPtr, IsBadWritePtr и IsBadStringPtr выполняют эту проверку. Первая из этих функций просто принимает указатель в качестве параметра

17

и возвращает ненулевое значение (TRUE), если указатель действителен. Другие три функции получают указатель в качестве первого параметра и длину блока памяти в качестве второго параметра. Четвертая функция, кроме того, осуществляет проверку до тех пор, пока не встретит нулевой ограничитель строки.

Функции управления виртуальной памятью

Windows 95 поддерживает ряд функций, начинающихся со слова Virtual. Эти функции предоставляют значительно больше возможностей управления памятью. Однако, только очень необычные приложения требуют использования этих функций.

Например, вы разрабатываете интегрированную среду разработчика, которая включает в себя компилятор и исполнительную систему. Программа читает исходный код и компилирует его, а результат компиляции заносится в память. Затем, вы хотите пометить этот блок памяти как "только для выполнения", т. е. так, чтобы было невозможно случайно или преднамеренно прочитать (или того хуже, записать) в него что-либо из программы, которая была только что скомпилирована и готова к выполнению. Это одно из действий, которое должна осуществлять развитая среда разработчика для обработки ошибок при работе с указателями в пользовательской программе. Вы можете также пометить часть блоков памяти "только для чтения". Оба указанных действия выполняются только с помощью функций управления виртуальной памятью. Но, повторяем, эта операция достаточно экзотическая.

Может быть, более часто возникает необходимость зарезервировать большой блок виртуальной памяти для данных, который может быть сильно увеличен в процессе выполнения программы. Действуя обычным путем — т. е. часто используя функции realloc или функцию Windows GlobalReAlloc для динамического изменения размера выделенного блока памяти — можно резко снизить производительность программы. Функции управления виртуальной памятью могут помочь избежать этого. Рассмотрим теперь, как это делается.

В Windows 95 любой блок виртуальной памяти может находиться в одном из трех состояний: "committed" (т. е. блок спроецирован в физическую память), "free" (свободен, т. е. доступен для будущего выделения), "reserved" (зарезервирован, это нечто среднее между двумя предыдущими состояниями). Зарезервированный блок виртуальной памяти не отображается в физическую память. Адреса в пределах этого блока будут недоступны до тех пор, пока всему блоку или его какой-либо части не будет передан блок физической памяти. Таким образом, вы можете зарезервировать достаточно большой блок виртуальной памяти, не передавая ему физической памяти. Когда будет необходимо обратиться по какому-либо виртуальному адресу в пределах этого блока, вы передаете по этому адресу ровно столько физической памяти, сколько необходимо, т. е. в зарезервированном блоке виртуальной памяти могут быть участки, как связанные, так и несвязанные с блоками физической памяти. Спроецировав физическую память на нужный участок зарезервированной области виртуальной памяти, программа может обращаться к нему, не вызывая при этом исключения нарушения доступа.

Для того чтобы использовать функции работы с виртуальной памятью, вашей программе необходимо знать размер страницы памяти. В отличие от Windows NT, Windows 95 работает только на микропроцессорах фирмы Intel, и размер страницы всегда равен 4096 байт. Если ваша программа разрабатывается также для запуска под Windows NT, используйте функцию GetSystemInfo для получения размера страницы. Эта функция имеет один параметр, который является указателем на структуру типа SYSTEM_INFO. Поле dwPageSize этой структуры содержит размер страницы. Используются также поля lpMinimumApplicationAddress и lpMaximumApplicationAddress, содержащие минимальный и максимальный адреса, имеющиеся в распоряжении приложения. Для Windows 95 эти значения равны соответственно 0x00400000 и 0x7FFFFFFF.

Функция VirtualAlloc выглядит следующим образом:

p = VirtualAlloc(pAddr, dwSize, iAllocType, iProtect);

Первый параметр показывает желаемый стартовый базовый адрес виртуальной памяти, и вы можете установить его значение в NULL при первом вызове этой функции. Второй параметр задает размер. Третий параметр может быть равен MEM_RESERVE или MEM_COMMIT для резервирования блока виртуальной памяти или для резервирования и передачи ему физической памяти. Четвертый параметр может быть константой, начинающейся с префикса PAGE_ (например, PAGE_READONLY или PAGE_EXECUTE) для задания защиты блока памяти. Последовательные вызовы функции VirtualAlloc могут передавать или резервировать секции этого блока. Функция VirtualFree используется для освобождения виртуальной памяти.

Функции работы с "кучей"

Последняя группа функций работы с памятью — это функции, имена которых начинаются со слова Heap (куча). Эти функции создают и поддерживают непрерывный блок виртуальной памяти, из которого вы можете выделять память более мелкими блоками. Вы начинаете с вызова функции HeapCreate. Затем, используете функции HeapAllocate, HeapReAllocate и HeapFree для выделения и освобождения блоков памяти в рамках "кучи". "Куча" может быть уплотнена для объединения свободного пространства.

Соседние файлы в предмете Операционные системы