Что делает VirtualFree ?

Тема в разделе "WASM.WIN32", создана пользователем cresta, 3 фев 2005.

  1. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Приветствую всех. Есть вопрос о поведении VirtualFree. Краткое описание: две области памяти, выделенные VirtualAlloc: hMemTask размером TaskSize и hMemAddr размером AddrSize. Если AddrSize=0, то при удалении hMemAddr удаляется другой кусок: hMemTask. Почему?


    Код (Text):
    1. .data
    2.     hMemTask    dd  ?
    3.     hMemAddr    dd  ?
    4. .code
    5.     invoke VirtualAlloc,NULL,TaskSize,MEM_COMMIT,PAGE_READWRITE
    6.     mov hMemTask,eax
    7.     invoke VirtualAlloc,NULL,AddrSize,MEM_COMMIT,PAGE_READWRITE
    8.     mov hMemAddr,eax
    9.     .
    10.     .
    11.     .
    12.     mov eax,hMemTask          ;здесь заносим 1 без проблем
    13.     mov dword ptr [eax],1
    14.     PrintText "Before VirtualFree hMemAddr"
    15.     invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT
    16.     mov eax,hMemTask          ;а вот тут - программа вылетает
    17.     mov dword ptr [eax],1
    18.     PrintText "After VirtualFree hMemAddr"
    19.     ;до печати строки "After VirtualFree hMemAddr" не доходит :(
    20.  




    Почему попытка освободить память размером 0 байт приводит к освобождению другого куска памяти? Хэндлы, возвращаемые VirtualAlloc, различные для этих двух кусков.



    Если сделать страховку, например:


    Код (Text):
    1.     .if (AddrSize)
    2.         invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT
    3.     .endif
    4.  




    то hMemTask остается нестертой, и я могу продолжать с ней работать.



    Кто-нибудь сталкивался с таким? Отчего такое?
     
  2. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Не могу воспроизвести (XP SP2, 98SE)

    У меня вызов VirtualAlloc с dwSize = 0 возвращает 0.

    Соответственно VirtualFree ничего не делает, если ей предать такой параметр.
     
  3. dragon

    dragon New Member

    Публикаций:
    0
    Регистрация:
    5 ноя 2002
    Сообщения:
    84
    Адрес:
    Питер
    cresta

    Возможно эти два куска следуют друг за другом и удаляется целый регион памяти. Хотя действительно странно как VirtualAlloc может выделить блок памяти с размером 0.
     
  4. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    В 9х тоже ничего подобного не происходит.

    Все как положено - при Size=0 VirtualAlloc и VirtualFree ничего не делают и возвращают 0, устанавливая GetLastError <> 0.



    Ищи ошибку: VirtualQuery + отладчик
     
  5. infern0

    infern0 New Member

    Публикаций:
    0
    Регистрация:
    7 окт 2003
    Сообщения:
    811
    Адрес:
    Russia
    Код (Text):
    1. LPVOID VirtualAlloc(
    2.   LPVOID lpAddress,
    3.   SIZE_T dwSize,
    4.   DWORD flAllocationType,
    5.   DWORD flProtect
    6. );




    и далее:

    dwSize

    [in] The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress to (lpAddress+dwSize). This means that a 2-byte range straddling a page boundary causes both pages to be included in the allocated region.
     
  6. dragon

    dragon New Member

    Публикаций:
    0
    Регистрация:
    5 ноя 2002
    Сообщения:
    84
    Адрес:
    Питер
    infern0

    Не путай, тут не lpAddress равен нулю, а dwSize.
     
  7. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Тут такое вот дело, в свете:







    Вот что возвращает VirtualAlloc (ни разу не ноль):

    hMemTask = 14090240 (Strings.INC, 442)

    TaskSize = 7020 (Strings.INC, 444)

    hMemAddr = 1244900 (Strings.INC, 443)

    AddrSize = 0 (Strings.INC, 445)



    Причем значение 1244900 я заношу в hMemAddr без проверки, сразу из eax в hMemAddr. Т.е. это именно то, что вернула ф-ция. В hex-виде 12FEE4h. Что-то маленькое значение больно.

    Когда AddrSize!=0, то возвращается число примерно равное hMemTask, т.е. около D70000h +/- килобайт 30-40.



    Может по адресу 12FEE4h лежит чего-то недоступное для записи/чтения/очищения ?



    P.S.

    Система - XP Home SP1
     
  8. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




    У всех ноль, а у тебя не ноль, что за винда?







    Похоже на адрес стека, неужели тяжело пройтись в отладчике по ф-ции



    [​IMG] _162067789__alloc.zip
     
  9. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    12FEE4h - это адрес в стэке, VirtualAlloc не может такого вернуть. смотри в отладчике где ашипка.
     
  10. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Ok, ребята, все понял. Подсказка со стеком прояснила дело:



    Процедура вызывается для чтения файла и записи его содержимого в память. От параметра dwFileType зависит, в какую память будет записываться. Процедура одна для нескольких файлов,
    Код (Text):
    1. LoadTask proc   dwFileType:DWORD
    2.     LOCAL Buff[260]        :BYTE
    3.     LOCAL hFile            :DWORD
    4.     LOCAL Readed           :DWORD
    5.     [b]LOCAL[/b] MemHandle        :DWORD       
    6.     LOCAL FileSize         :DWORD      
    7.     LOCAL DataCount        :DWORD      
    8.    
    9.     PrintHex MemHandle
    10.     ;[b]здесь вот собака порылась: MemHandle==12FEE4h[/b]
    11.     ;уже на входе в процедуру
    12.    
    13.     invoke GetFileSize,
    14.     test eax,eax
    15.     jz @Ret
    16.     invoke VirtualAlloc........
    17.     mov MemHandle,eax
    18.     .
    19.     .
    20.     .
    21. @Ret:
    22.     .if dwFileType==0
    23.         m2m hMemTask,MemHandle
    24.         m2m TaskSize,FileSize
    25.     .elseif dwFileType==1
    26.         m2m hMemAddr,MemHandle
    27.         m2m AddrSize,FileSize
    28.  
    29.  




    При первом заходе первым может считаться как файл нулевого размера, так и другой файл, ненулевой. Т.к. локальная MemHandle==12FEE4h, то она может попасть в hMemAddr вызывая затем попытку "освобождения" стека.

    И ещё такой момент: если первый заход в процедуру окончился получением валидного указателя (файл ненулевой) и сохранением его в hMemTask, то при следующем заходе в стеке остается этот хэндл и попадает в hMemAddr. Т.е. два куска с одним адресом, и пытаясь удалить hMemAddr, я удаляю hMemTask.

    А страховка в виде

    .if (AddrSize)

    invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT

    .endif

    предотвращает удаление hMemAddr и заодно и hMemTask, т.к. AddrSize=0, хотя в hMemAddr по ошибке попал указатель другого куска памяти.



    В связи с чем ещё один вопрос: что в стеке при входе в процедуру? Почему не нули? Откуда 12FEE4h?

    Неужели надо все локальные принудительно обнулять?
     
  11. bogrus

    bogrus Active Member

    Публикаций:
    0
    Регистрация:
    24 окт 2003
    Сообщения:
    1.338
    Адрес:
    ukraine




    Стек это обычный участок памяти, наполненный мусором
     
  12. dragon

    dragon New Member

    Публикаций:
    0
    Регистрация:
    5 ноя 2002
    Сообщения:
    84
    Адрес:
    Питер




    Да, если это необходимо, редко когда там нули бывают
     
  13. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Ясно. Теперь буду следить за этим.



    Спасибо всем.
     
  14. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Пара замечаний в догонку.



    invoke VirtualAlloc,Null,dwSize,MEM_COMMIT,PAGE_... при dwSize > 0 резервирует незанятое адресное пространство (MEM_RESERVE) размером RoundUp(dwSize,64Kб) и выделяет память (MEM_COMMIT) размером RoundUp(dwSize,4Кб) по начальному адресу зарезервированного блока.



    В связи с этим

    1) Адрес выделенного блока всегда выровнен по границе 64К. Поэтому hMemTask=D70000h это правильно, а вот hMemAddr=hMemTask +/- килобайт 30-40 - это фигня, должно быть D80000h или другое значение кратное 64K = 10000h.



    2) После invoke VirtualFree,lpAddress,dwSize,MEM_DECOMMIT адресное пространство lpAddress остается зарезервированным, и при последующих вызовах VirtualAlloc(Null,...) каждый раз будут резервироваться новые адреса. Чтобы этого не происходило нужно после освобождения памяти дополнительно освобождать и адресное пространство invoke VirtualFree,lpAddress,0,MEM_RELEASE.



    3) Если заранее известно, что размер файла не может превышать X Mb, то можно сразу зарезервировать (MEM_RESERVE) два адресных пространства для hMemTask и hMemAddr и затем делать только commit\decommit для каждого файла (а может и decommit не нужно - зависит от задачи). При этом адреса hMemTask и hMemAddr будут постоянными и мороки ИМХО меньше.
     
  15. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine


    Допустим, но
    Код (Text):
    1.  
    2. invoke VirtualAlloc,NULL,TaskSize,MEM_COMMIT,PAGE_READWRITE
    3. [b]mov hMemTask,eax[/b]


    должно обнулить hMemTask.
     
  16. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    IceStudent

    Да не-е. Ошибка в том, что при Size = 0 сразу прыжок на @Ret и запись неинициализированного значения MemHandle в hMemTask или hMemAddr. Кстати из приведенного кода и FileSize тоже м.б. неинициализирована.
     
  17. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    cresta



    мне кажется условные операторы лучше бы поставить перед @Ret



    и ещё, поскольку используешь шаманские операции со значениями возвращаемыми VirtualAlloc, можно учесть, что это не какой-то там абстрактный "handle", а обычный адрес. поэтому можно использовать его "магические" свойства: старший бит равен 0, младший байт (обычно) тоже. можно хранить флаги непосредственно в них, чем в каких-то других местах имеющих другую область видимости / жизни.



    а лучше почитай Alen I. Holub. "Enough Rope to Shoot Yourself in the Foot" там куча аргументов за то, что бы так не делать :).
     
  18. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    leo



    Сколько будет размер - неизвестно,поэтому, чтобы не морочиться ещё и с VirtualFree...MEM_RELEASE, может лучше вернуться к старому доброму GlobalAlloc? По крайней мере, кода меньше будет :)



    S_T_A_S_

    Да, согласен, что криво было сделано :dntknw:

    Сейчас переделал всё, никаких локальных, никаких условных переходов. Сразу передаю как параметры offset'ы конкретных ячеек хэндла, размера, имени файла и т.д. Параметров ф-ции стало 5 вместо одного, но от .if-elseif ушел
     
  19. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    cresta

    > "может лучше вернуться к старому доброму GlobalAlloc?"



    Можно, конечно, если не хочется возиться. Но если обрабатывается большое число файлов разного размера, то может получиться не дзенно - все зависит от того в какой последовательности выделяется и освобождается память под hMemTask и hMemAddr и не выделяется ли динамическая память под другие переменные в процессе обработки файлов. Если hMemTask и hMemAddr освобождаются друг за другом (т.е. между ними нет Alloc), то все нормально. Иначе можно столкнуться с фрагментацией кучи, а это задержки на поиск подходящего свободного места и увеличение объема выделенной (commit) памяти. Не катастрофично, но ИМХО не дзенно.



    Я в подобных случаях предпочитаю немного поморочиться: выделяю буфер FixBuf фиксированного размера FixSize; если Size <= FixBuf, то Buf = FixBuf, если же больше, то выделяем новый Buf = TempBuf нужного размера. При освобождении Buf проверяем Size: если > FixSize, то Free, иначе ничего не делаем. Кстати аналогичным образом работают и все менеджеры кучи при запросе больших блоков памяти > 64Кб или 1Мб (см. bigalloc в C++).



    В твоем случае, можно написать парочку функций типа MyAlloc и MyFree, которые будут в зависимости от Size работать с fix или temp буферами. Хотя не знаю, может в данном случае это из пушки по воробьям ? Смотри сам
     
  20. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    cresta



    Если ты аллоцируешь память, а потом туда читаешь файлы, то не проще ли сразу эти файлы мапить?



    ЗЫ: про GlobalAlloc imho лучше забыть, эта ф-ция для 3.11 и там вроде кое-какие глюки есть.