Приветствую всех. Есть вопрос о поведении VirtualFree. Краткое описание: две области памяти, выделенные VirtualAlloc: hMemTask размером TaskSize и hMemAddr размером AddrSize. Если AddrSize=0, то при удалении hMemAddr удаляется другой кусок: hMemTask. Почему? Код (Text): .data hMemTask dd ? hMemAddr dd ? .code invoke VirtualAlloc,NULL,TaskSize,MEM_COMMIT,PAGE_READWRITE mov hMemTask,eax invoke VirtualAlloc,NULL,AddrSize,MEM_COMMIT,PAGE_READWRITE mov hMemAddr,eax . . . mov eax,hMemTask ;здесь заносим 1 без проблем mov dword ptr [eax],1 PrintText "Before VirtualFree hMemAddr" invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT mov eax,hMemTask ;а вот тут - программа вылетает mov dword ptr [eax],1 PrintText "After VirtualFree hMemAddr" ;до печати строки "After VirtualFree hMemAddr" не доходит :( Почему попытка освободить память размером 0 байт приводит к освобождению другого куска памяти? Хэндлы, возвращаемые VirtualAlloc, различные для этих двух кусков. Если сделать страховку, например: Код (Text): .if (AddrSize) invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT .endif то hMemTask остается нестертой, и я могу продолжать с ней работать. Кто-нибудь сталкивался с таким? Отчего такое?
Не могу воспроизвести (XP SP2, 98SE) У меня вызов VirtualAlloc с dwSize = 0 возвращает 0. Соответственно VirtualFree ничего не делает, если ей предать такой параметр.
cresta Возможно эти два куска следуют друг за другом и удаляется целый регион памяти. Хотя действительно странно как VirtualAlloc может выделить блок памяти с размером 0.
В 9х тоже ничего подобного не происходит. Все как положено - при Size=0 VirtualAlloc и VirtualFree ничего не делают и возвращают 0, устанавливая GetLastError <> 0. Ищи ошибку: VirtualQuery + отладчик
Код (Text): LPVOID VirtualAlloc( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ); и далее: 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.
Тут такое вот дело, в свете: Вот что возвращает 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
У всех ноль, а у тебя не ноль, что за винда? Похоже на адрес стека, неужели тяжело пройтись в отладчике по ф-ции _162067789__alloc.zip
Ok, ребята, все понял. Подсказка со стеком прояснила дело: Процедура вызывается для чтения файла и записи его содержимого в память. От параметра dwFileType зависит, в какую память будет записываться. Процедура одна для нескольких файлов, Код (Text): LoadTask proc dwFileType:DWORD LOCAL Buff[260] :BYTE LOCAL hFile :DWORD LOCAL Readed :DWORD [b]LOCAL[/b] MemHandle :DWORD LOCAL FileSize :DWORD LOCAL DataCount :DWORD PrintHex MemHandle ;[b]здесь вот собака порылась: MemHandle==12FEE4h[/b] ;уже на входе в процедуру invoke GetFileSize, test eax,eax jz @Ret invoke VirtualAlloc........ mov MemHandle,eax . . . @Ret: .if dwFileType==0 m2m hMemTask,MemHandle m2m TaskSize,FileSize .elseif dwFileType==1 m2m hMemAddr,MemHandle m2m AddrSize,FileSize При первом заходе первым может считаться как файл нулевого размера, так и другой файл, ненулевой. Т.к. локальная MemHandle==12FEE4h, то она может попасть в hMemAddr вызывая затем попытку "освобождения" стека. И ещё такой момент: если первый заход в процедуру окончился получением валидного указателя (файл ненулевой) и сохранением его в hMemTask, то при следующем заходе в стеке остается этот хэндл и попадает в hMemAddr. Т.е. два куска с одним адресом, и пытаясь удалить hMemAddr, я удаляю hMemTask. А страховка в виде .if (AddrSize) invoke VirtualFree,hMemAddr,AddrSize,MEM_DECOMMIT .endif предотвращает удаление hMemAddr и заодно и hMemTask, т.к. AddrSize=0, хотя в hMemAddr по ошибке попал указатель другого куска памяти. В связи с чем ещё один вопрос: что в стеке при входе в процедуру? Почему не нули? Откуда 12FEE4h? Неужели надо все локальные принудительно обнулять?
Пара замечаний в догонку. 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 будут постоянными и мороки ИМХО меньше.
Допустим, но Код (Text): invoke VirtualAlloc,NULL,TaskSize,MEM_COMMIT,PAGE_READWRITE [b]mov hMemTask,eax[/b] должно обнулить hMemTask.
IceStudent Да не-е. Ошибка в том, что при Size = 0 сразу прыжок на @Ret и запись неинициализированного значения MemHandle в hMemTask или hMemAddr. Кстати из приведенного кода и FileSize тоже м.б. неинициализирована.
cresta мне кажется условные операторы лучше бы поставить перед @Ret и ещё, поскольку используешь шаманские операции со значениями возвращаемыми VirtualAlloc, можно учесть, что это не какой-то там абстрактный "handle", а обычный адрес. поэтому можно использовать его "магические" свойства: старший бит равен 0, младший байт (обычно) тоже. можно хранить флаги непосредственно в них, чем в каких-то других местах имеющих другую область видимости / жизни. а лучше почитай Alen I. Holub. "Enough Rope to Shoot Yourself in the Foot" там куча аргументов за то, что бы так не делать .
leo Сколько будет размер - неизвестно,поэтому, чтобы не морочиться ещё и с VirtualFree...MEM_RELEASE, может лучше вернуться к старому доброму GlobalAlloc? По крайней мере, кода меньше будет S_T_A_S_ Да, согласен, что криво было сделано Сейчас переделал всё, никаких локальных, никаких условных переходов. Сразу передаю как параметры offset'ы конкретных ячеек хэндла, размера, имени файла и т.д. Параметров ф-ции стало 5 вместо одного, но от .if-elseif ушел
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 буферами. Хотя не знаю, может в данном случае это из пушки по воробьям ? Смотри сам
cresta Если ты аллоцируешь память, а потом туда читаешь файлы, то не проще ли сразу эти файлы мапить? ЗЫ: про GlobalAlloc imho лучше забыть, эта ф-ция для 3.11 и там вроде кое-какие глюки есть.