воскресенье, 4 октября 2009 г.

В чём разница между LocalAlloc и GlobalAlloc?

Это перевод What was the difference between LocalAlloc and GlobalAlloc? Автор: Реймонд Чен.

Когда-то во времена 16-ти битных Windows разница была огромной.

В 16-ти битных Windows доступ к памяти получали через значения, называемые "селекторами" ("selectors"), каждый из которых позволял адресовать память до 64 Кб. Существовал селектор по-умолчанию, который назывался "селектором данных" ("data selector"); операции с так называемыми "ближними указателями" ("near pointers") осуществлялись относительно селектора данных. Например, если у вас был ближний указатель p со значением $1234 и ваш селектор данных был $012F, то тогда, когда вы писали p^, вы получали доступ к памяти по адресу 012F:1234 (когда вы объявляли указатель, он по-умолчанию считался ближним. Вам нужно было явно приписывать far, чтобы указать, что вам нужен дальний указатель).

Важный момент: ближние указатели всегда относительны к селектору, обычно: к селектору данных.

Функция GlobalAlloc выделяет селектор, который может быть использован для доступа к запрошенному вами количеству памяти (а если вы попросите выделить вам больше 64 Кб памяти, то произойдёт нечто волнующее, что, впрочем, сейчас не важно). Вы можете получить доступ к памяти по этому селектору через "дальний указатель" ("far pointer"). "Дальний указатель" - это селектор, соединённый с ближним указателем (не забывайте: ближний указатель относителен к селектору; когда вы соединяете ближний указатель с соответствующим ему селектором, вы получаете дальний указатель).

Каждый экземпляр (instance) программы и DLL имеет свой собственный селектор данных, известный как HINSTANCE, который я описывал ранее. Селектор данных по-умолчанию для кода в программе был HINSTANCE этого экземпляра программы; селектор данных по-умолчанию для DLL был HINSTANCE этой DLL. Поэтому, если у вас был ближний указатель p, то разыменовывая его как p^ из кода программы, вы получали доступ к памяти относительно HINSTANCE экземпляра программы. Если вы разыменовывали его в DLL, то вы получали память, относительно HINSTANCE вашей DLL.

Память, на которую ссылается селектор по-умолчанию, может быть превращена в "локальную кучу" ("local heap") вызовом функции LocalInit. Инициализация локальной кучи обычно была одной из первых вещей, которые делала программа или DLL при запуске (для DLL это было обычно единственной вещью, которую она делала при загрузке!). Когда у вас есть локальная куча, вы можете вызывать LocalAlloc для выделения из неё памяти. Функция LocalAlloc возвращала ближний указатель, относительный к селектору по-умолчанию, так что если вы вызывали её из программного модуля, она выделяла память для HINSTANCE программы; если вы вызывали её из DLL, она выделяла память из HINSTANCE DLL.

Если вы были сообразительны, вы могли понять, что вы можете использовать LocalAlloc для выделения памяти из других блоков, не только из HINSTANCE. Всё, что вам нужно было для этого сделать, так это изменить ваш селектор по-умолчанию на селектор для памяти, выделенной через GlobalAlloc, вызвать функцию LocalAlloc, а затем восстановить селектор по-умолчанию. Это давало вам ближний указатель, относительный к чему-то, отличному от селектора по-умолчанию, что было очень страшной вещью. Но если вы были умны и аккуратно всё делали, то вы могли избежать неприятностей.

Заметьте, что в 16-ти битных Windows, функции LocalAlloc и GlobalAlloc были совершенно различными! LocalAlloc возвращала ближний указатель, в то время как GlobalAlloc вообще возвращала селектор.

Указатели, которые вы хотели передавать между модулями, должны были иметь вид "дальних указателей", потому что у каждого модуля был свой селектор по-умолчанию. Если вы хотели передать владение памятью в другой модуль, вам нужно было использовать GlobalAlloc, потому что это позволяло вызывающему использовать потом вызов GlobalFree для его освобождения (вызывающий не мог использовать LocalFree, потому что LocalFree работает в локальной куче, которая будет кучей вызывающего - это не то же самое, что ваша куча).

Эта историческая разница между локальной и глобальной памятью до сих пор остаётся в Win32. Если у вас есть функция, которая пришла из 16-ти битных Windows, и она передаёт право на память, то она будет работать со значениями типа HGLOBAL. Классическим примером таких функций являются функции буфера обмена (clipboard). Если вы размещаете блок памяти в буфере обмена, он должен быть выделен через HGLOBAL, потому что вы передаёте память буферу обмена, и он вызовет GlobalFree, когда эта память станет ему не нужна. Память, передаваемая через STGMEDIUM, принимает форму HGLOBAL по той же причине.

Даже в Win32, вам нужно быть осторожными и не перепутать локальную кучу и глобальную. Память, выделенная в одной, не может быть освобождена в другой. Хотя функциональные различия в основном исчезли; и семантика к этому моменту практически идентична. Все странности по ближним и дальним указателям тоже исчезли при переходе к Win32. Но функции локальной и глобальной куч, тем не менее, остаются двумя различными и не связанными интерфейсами куч.

Я собираюсь потратить несколько записей на описание возможностей 16-ти битного менеджера памяти. Хотя сегодня вам не нужно знать их, имея перед собой историю, вы можете понять некоторые подводные камни менеджера памяти Win32. Сегодня мы уже чуть-чуть увидели это, когда логика 16-ти битного менеджера памяти установила правила для буфера обмена.

8 комментариев:

  1. Здравствуйте! Скажите пожалуйста, Вы сами знали предметную область данной статьи, когда начали перевод, или же Вы просто перевели всё так как есть? К предметной области я бы отнёс ассемблер, эволюцию моделей памяти, селекторы, преобразование адреса, страничное и сегментное преобразования? Вы сами когда-нибудь создавали сегменты, обращались к ним? Просто данная статья сильно специфична и Раймонд Чен, её автор, знает о чём пишет, а Вы переводите её, снижая качество статьи на уровне терминологии, что у меня вызывает недоверие. Я думаю, именно потому, что Вы не совсем знаете предметную область. Могу поправить, если позволите.

    ОтветитьУдалить
  2. Почему бы просто не написать, где я ошибся? :)

    ОтветитьУдалить
  3. Этот комментарий был удален автором.

    ОтветитьУдалить
  4. Для начала ну чтобы понять просто с чем имеете дело, Попробуйте прочитать чтонибудь о таблице gdt регистрах селекторах , которые к слову появились уже вместе с защищённым режимом процессора и содержат в себе теневые 48 битные регистры (в 32 битных процессорах). Потом сравните Global Alloc с VirtalAlloc
    Это очень поможет в ваших изысканиях:)

    ОтветитьУдалить
  5. Дело том, что GlobalAlloc возвращает дескриптор в неком ассоциативном списке памяти,
    HGLOBAL hGlob =GlobalAlloc(GPTR | GMEM_SHARE,
    sizeof(ClrBuffer));
    а вот GlobalLock () - возвращает указатель. Примерно так.
    Image->Pixels = reinterpret_cast(GlobalLock(hGlob));
    А если ещё точнее
    http://www.vsokovikov.narod.ru/New_MSDN_API/Compatibility_16_32/fn_globallock.htm
    (В линейках ОС Windows NT и 9x не используется)
    Зачем вообще о ней писать?

    ОтветитьУдалить
  6. А если прочесть в msdn получается совсем интересно:

    Windows memory management does not provide a separate local heap and global heap, as 16-bit Windows does. As a result, the global and local families of functions are equivalent and choosing between them is a matter of personal preference. Note that the change from a 16-bit segmented memory model to a 32-bit virtual memory model has made some of the related global and local functions and their options unnecessary or meaningless. For example, there are no longer near and far pointers, because both local and global allocations return 32-bit virtual addresses.

    Получется что GlobalAlloc и LocalAlloc в 16 битных версиях Windows производили разделение между сегментами памяти локальной и глобальной кучи. Очевидно
    что в win16 куча подразделялась на глобальную и локальную занимая соответственно либо один сегмент вместе с процессом(локальная)
    тогда доступ к памяти происходил примерно так mov eax,dword ptr[VasyaArray],или специальный глобальный сегмент кучи- в этом случае данные между процессами расшаривались и доступ должен был производиться с помощью дальних указателей:
    разница между указателями отлично показана тут:
    http://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8_Intel_x86

    , очевидно что Win32 локальная и глобальная функции суть одно и тоже, поскольку доступ к памяти FLAT плоская
    тут подробно:
    http://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%B8_Intel_x86
    Нет никакой разницы, как организован указатель. .

    ОтветитьУдалить
  7. Я повторю вопрос: почему бы не написать, где я ошибся? К примеру: "вот здесь Реймонд Чен написал X, вы перевели это как Y, хотя правильнее было бы перевести это как Z". Пока я совершенно не понимаю, что вы хотите сказать.

    ОтветитьУдалить
  8. Алексей, интересная статья по истории. А текущая ситуация описывается двумя абзацами из MSDN

    Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc have greater overhead than HeapAlloc.

    Because the different heap allocators provide distinctive functionality by using different mechanisms, you must free memory with the correct function. For example, memory allocated with HeapAlloc must be freed with HeapFree and not LocalFree or GlobalFree. Memory allocated with GlobalAlloc or LocalAlloc must be queried, validated, and released with the corresponding global or local function.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.