⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 010.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 5 页
字号:
    .if   eax

    add   dwTotalMemory,1000000

    inc   dwCount

    .endif

    pop   eax

    invoke  GlobalReAlloc,eax,100,GMEM_ZEROINIT

    sub   dwTotalMemory,1000000 - 100

    invoke  SetDlgItemInt,hWinMain,IDC_MEMORY,\

    dwTotalMemory,FALSE

    invoke  SetDlgItemInt,hWinMain,IDC_COUNT,\

    dwCount,FALSE

    .until  ! @lpLastMem

    invoke  SetDlgItemText,hWinMain,IDC_INFO,addr szInfo

    mov   ifCanQuit,1

    ret

 

_ProcThread   endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcDlgMain     proc     uses ebx edi esi hWnd,wMsg,wParam,lParam

    local @dwTemp

 

    mov   eax,wMsg

    .if   eax ==  WM_CLOSE

    .if   ifCanQuit

    invoke  EndDialog,hWnd,NULL

    .endif

;********************************************************************

    .elseif eax ==  WM_INITDIALOG

    push     hWnd

    pop   hWinMain

    invoke  LoadIcon,hInstance,ICO_MAIN

    invoke  SendMessage,hWnd,WM_SETICON,ICON_BIG,eax

    invoke CreateThread,NULL,0,offset _ProcThread,NULL,\

      NULL,addr @dwTemp

    invoke  CloseHandle,eax

;********************************************************************

    .else

    mov eax,FALSE

        ret

    .endif

    mov   eax,TRUE

    ret

 

_ProcDlgMain     endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

start:

    invoke  GetModuleHandle,NULL

    mov   hInstance,eax

    invoke  DialogBoxParam,hInstance,DLG_MAIN,\

    NULL,offset _ProcDlgMain,NULL

    invoke  ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    end   start

 


对应的资源文件Fragment.rc如下:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#include     

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#define ICO_MAIN     1000

#define DLG_MAIN     100

#define IDC_MEMORY     101

#define IDC_COUNT   102

#define IDC_INFO     103

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN     ICON         "Main.ico"

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

DLG_MAIN DIALOG 308, 207, 130, 50

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "碎片内存演示"

FONT 9, "宋体"

{

 RTEXT "申请内存总数:", -1, 7, 8, 60, 8

 EDITTEXT IDC_MEMORY, 69, 5, 55, 12, 

ES_AUTOHSCROLL | ES_READONLY | WS_BORDER | WS_TABSTOP

 RTEXT "申请次数:", -1, 7, 21, 60, 8

 EDITTEXT IDC_COUNT, 69, 19, 55, 12, 

  ES_AUTOHSCROLL | ES_READONLY | WS_BORDER | WS_TABSTOP

 LTEXT "", IDC_INFO, 7, 37, 120, 8

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

程序在WM_INITDIALOG消息中建立了一个线程来循环申请内存(相当于在后台执行_ProcThread子程序,与多线程相关的内容请参见第12章)。全局变量dwCount记录了申请的次数,每次申请内存就将它的值加1。dwTotalMemory记录了程序申请到的内存总数,每申请一个1 MB的内存,程序将它的值加上1 000 000,每次用GlobalReAlloc缩小内存块,则将它的值减去999 900。当最后申请内存失败的时候,repeat循环结束。

在Windows 2000下运行一下程序以验证结果,几秒的运行中,显示的计数不断增加,最后的结果如图10.3所示。



图10.3  内存碎片化的演示结果

结果和预想的一样,经过2 027次的操作,只保留了近202 700 B的内存,程序就成功地“谋杀”了所有的地址空间,让整个2 GB中间充满了碎片,以至于连1 MB大小的内存也无法申请了!当程序在Windows 9x中运行时,由于9x系统在高端和低端轮换分配内存块,所以同样的办法就不会产生内存碎片,但是如果在循环中先Alloc两次、然后Realloc两次的话仍然可以造成内存碎片化。

虽然这是一个极端的情况,但在现实中会发生吗?会的!例如编写一个遍历二叉树的程序,每增加一个结点的时候申请一块内存,用来存放指向其他结点的指针以及附加在结点上的数据,当结点处理完毕后缩小内存块,只留下指针数据,那么情况就和演示程序类似,当树的结点足够多的时候,经过一段时间的操作,内存中就会充满碎片。

解决内存碎片化的办法很简单,因为碎片之间有大量的内存是空闲的,只要允许Windows移动小块的在用内存,就可以将碎片合并成大块的空闲内存,但是在用内存被移动后,程序中对应的指针也要随着改变,不然就会访问到错误的地址,而且,在使用内存的过程中,内存需要有个锁定的过程,否则用到一半的时候被Windows移动了,结果依然是错误的,只有程序将内存解锁,Windows才可以自由移动它们,这就引伸出了可移动内存块的概念和操作的基本方法。

要申请一个可移动的内存块,使用的函数还是GlobalAlloc,但需要使用不同的参数:

invoke  GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,dwBytes

.if eax

  mov hMemory,eax

.endif

GMEM_MOVEABLE标志指定了分配的内存是可移动的,GMEM_ZEROINIT同样表示将申请到的内存块的内容初始化为0(也可以用GHND标志,它就相当于GMEM _MOVEABLE or GMEM_ZEROINIT);如果内存申请失败,eax中返回NULL,成功的话返回值是一个句柄而不是内存指针,用户需要保存这个句柄,在锁定或释放内存的时候还要用到它。一个进程可以申请的可移动内存的块数最大不能超过65 536个,申请固定内存块时则没有数量限制。

要使用可移动内存之前,需要把它锁定,这相当于告诉Windows现在程序要使用这块内存了,不能将它移动,锁定内存使用GlobalLock函数:

invoke  GlobalLock,hMemory

.if eax

  mov lpMemory,eax

.endif

函数的入口参数是GlobalAlloc返回的内存句柄,如果锁定成功,函数返回一个指针,程序可以用使用固定内存块同样的方法来使用它;如果锁定失败,则函数返回NULL。每次锁定返回的指针位置可能是不同的,但内存块中的数据不会变化。

当程序暂时不需要操作这块内存的时候,应该将它解锁,否则和使用固定的内存块就没有区别了,解锁使用GlobalUnlock函数:

  invoke  GlobalUnlock,hMemory

函数的参数同样是GlobalAlloc返回的句柄,解锁成功的话函数返回非0值。读者可能有个问题:在多线程的程序中,两个地方同时锁定内存,但当一个地方还在使用的情况下另一个地方却调用GlobalUnlock将内存解锁了怎么办?其实不用担心这个问题,Windows为每个可移动的内存句柄维护一个锁定计数,每次锁定内存的时候计数加1,解锁的时候计数减1,只有当计数为0的时候内存才真正被解锁,所以只要程序中的GlobalLock函数和GlobalUnlock函数是配对的,就不用担心这个问题。

要释放一个可移动的内存块,同样使用GlobalFree函数:

  invoke  GlobalFree,hMemory

但使用的参数是GlobalAlloc返回的内存句柄,如果释放成功,函数返回NULL。不管内存当前是否处在锁定状态,都可以被成功释放。

调整可移动内存块的大小,同样使用GlobalReAlloc函数:

  invoke  GlobalReAlloc,hMemory,dwBytes,GMEM_ZEROINIT or GMEM_MOVEABLE

如果调整成功,返回值就是输入的hMemory,失败的话返回值是NULL。即使内存块在锁定状态,函数仍然可以调用成功,但这时候内存块可能已经被移动了位置,原来用GlobalLock函数获取的指针可能已经失效了,所以调整可移动内存块的大小最好还是先将内存解锁,等调整完毕以后再锁定使用。

由于使用可移动的内存块多了一个锁定的动作,速度自然要比使用固定的内存块要慢一点,但固定内存块又存在碎片问题,程序中使用哪种方法有个取舍的问题。如果程序要频繁地分配和释放不定长的内存块,内存的碎片化现象就比较严重,特别是当程序长时间运行时,这种情况下使用可移动内存块比较好;如果程序只进行少量的内存操作,或者虽然频繁分配和释放内存,但使用的内存块长度都是一样的,则使用固定内存块可以节省时间。

 

3. 可丢弃的内存块

分配可移动内存块的时候还可以配合GMEM_MOVEABLE标志使用GMEM_DI SCARDABLE标志,这样生成的内存块是可丢弃的内存块,表示当Windows急需内存使用的时候,可以将它从物理内存中丢弃,可丢弃的内存块首先必须是可移动的内存块。函数调用如下:

invoke  GlobalAlloc,GHND or GMEM_DISCARDABLE,dwBytes

.if eax

  mov hMemory,eax

.endif

当用GlobalLock锁定内存的时候如果返回NULL指针,表示内存已经被Windows丢弃了,当然其中的数据也丢失了,程序需要重新生成数据。当内存块被丢弃的时候,内存句柄还是有效的,如果程序还要使用这个句柄,那么可以对它使用GlobalReAlloc函数来重新分配内存。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -