The differences are:
1) There is no need to modify the affected function calls in your source, (no search and replace). Just add the include at the top of it.
When the debugger is enabled, the include code is tracking your functions calls, when the debugger is disabled, all is back to normal.
2) The trace data is added to the linked list only if the allocation if successful.
If an allocation fails I choosed to not add the relative trace data, because a correspondent FreeMemory() is not actually needed and there is no leakage.
Moreover I don't even know if we would have the memory to add a new item to the list !

3) The traced functions are listed at the top of the source.
I added the images-related commands because, at least in my case, I think another good source of problems are sometimes temporary images used in your procedures. If you forgot a FreeImage() and the *bad* procedure is called a lot, you waste a lot of memory pretty fast.
Probably you can extend it to track other functions, maybe even API functions, didn't try it but should work following the same template (a macro + a renamed proc).
There are so many possible causes for a memory leak this include maybe should be view more as a learning tool for people new to PB (to detect naive errors from the start) than a real useful aid to debugging, but anyway.... here it is.
The reported data is to be taken with a grain of salt. Not always, for example, a image not freed is a bad thing.
One case is when it's created by the program and then not freed just before its end. It will be freed anyway at the exit.
But as said above a temp image used in a procedure is another thing and you must keep an eye on them !
1.2 added locking for multithreading
1.3 added support for automatic deallocation of PB objects when using explicit numeric constants instead of #PB_Any
1.4 corrected a minor problem in the demo and updated the code for PB 5.20
If you want to test for GDI leakages in Windows a good tool is GDIView -> http://www.nirsoft.net/utils/gdi_handles.html
INCLUDE v1.4.1 - NoLeak.pbi
Code: Select all
EnableExplicit
;******************************************************************************
; NoLeak 1.4.1 by Luis
; This is updated for PB 5.20 and may not work with previous versions.
;
; The traced functions are:
;
; AllocateMemory (iBytes, iFlags = 0)
; ReAllocateMemory (iAddress, iBytes, iFlags = 0)
; FreeMemory (iAddress)
; LoadImage (iImageNum, Filename$, iFlags = 0)
; GrabImage (iImageSrc, iImageNum, iX, iY, iWidth, iHeight)
; CopyImage (iImageSrc, iImageNum)
; CatchImage (iImageNum, iMemoryAddress, iSize = $7FFFFFFF)
; CreateImage (iImageNum, iWidth, iHeight, iDepth = 24, iBackColor = RGB(0,0,0))
; FreeImage (iImageNum)
; LoadFont (iFontNum, Filename$, iYSize, iFlags = #PB_Font_HighQuality)
; FreeFont (iFontNum)
;******************************************************************************
CompilerIf #PB_Compiler_Debugger
CompilerIf (#PB_Compiler_Thread = 1)
Global hLeakMutex = CreateMutex()
Macro M_LockMutex(mutex)
LockMutex(mutex)
EndMacro
Macro M_UnlockMutex(mutex)
UnlockMutex(mutex)
EndMacro
CompilerElse
Macro M_LockMutex(mutex)
EndMacro
Macro M_UnlockMutex(mutex)
EndMacro
CompilerEndIf
Structure T_NOLEAK_FONT
Source$
iLine.i
iFontNum.i
Filename$
iYsize.i
EndStructure : NewList NoLeak_FONT.T_NOLEAK_FONT()
Structure T_NOLEAK_ALLOC
Source$
iLine.i
iAddress.i
iBytes.i
EndStructure : NewList NoLeak_ALLOC.T_NOLEAK_ALLOC()
Structure T_NOLEAK_IMAGE
Source$
iLine.i
iImageNum.i
iWidth.i
iHeight.i
iDepth.i
EndStructure : NewList NoLeak_IMAGE.T_NOLEAK_IMAGE()
;{ FreeFont()
Procedure _FreeFont (iFontNum, iOverWritten = 0)
Shared NoLeak_FONT()
M_LockMutex(hLeakMutex)
ForEach NoLeak_FONT()
If NoLeak_FONT()\iFontNum = iFontNum
DeleteElement(NoLeak_FONT())
Break
EndIf
Next
M_UnlockMutex(hLeakMutex)
If iOverWritten = 0
FreeFont(iFontNum)
EndIf
EndProcedure
Macro FreeFont (iFontNum)
_FreeFont (iFontNum)
EndMacro
;}
;{ LoadFont()
Procedure.i _LoadFont (Source$, iLine, iFontNum, Filename$, iYSize, iFlags)
Shared NoLeak_FONT()
Protected iRetVal
iRetVal = LoadFont (iFontNum, Filename$, iYSize, iFlags)
If iRetVal
If iFontNum <> #PB_Any
_FreeFont(iFontNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_FONT())
NoLeak_FONT()\Source$ = Source$
NoLeak_FONT()\iLine = iLine
NoLeak_FONT()\Filename$ = Filename$
NoLeak_FONT()\iYsize = iYsize
If iFontNum = #PB_Any
NoLeak_FONT()\iFontNum = iRetVal
Else
NoLeak_FONT()\iFontNum = iFontNum
EndIf
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro LoadFont (iFontNum, Filename, iYSize, iFlags = #PB_Font_HighQuality)
_LoadFont (#PB_Compiler_File, #PB_Compiler_Line, iFontNum, Filename, iYSize, iFlags)
EndMacro
;}
;{ AllocateMemory()
Procedure.i _AllocateMemory (Source$, iLine, iBytes, iFlags)
Shared NoLeak_ALLOC()
Protected iRetVal
iRetVal = AllocateMemory (iBytes, iFlags)
If iRetVal
M_LockMutex(hLeakMutex)
AddElement(NoLeak_ALLOC())
NoLeak_ALLOC()\Source$ = Source$
NoLeak_ALLOC()\iLine = iLine
NoLeak_ALLOC()\iBytes = iBytes
NoLeak_ALLOC()\iAddress = iRetVal
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro AllocateMemory (iBytes, iFlags = 0)
_AllocateMemory (#PB_Compiler_File, #PB_Compiler_Line, iBytes, iFlags)
EndMacro
;}
;{ ReAllocateMemory()
Procedure.i _ReAllocateMemory (Source$, iLine, iAddress, iBytes, iFlags)
Shared NoLeak_ALLOC()
Protected iRetVal
iRetVal = ReAllocateMemory (iAddress, iBytes, iFlags)
If iRetVal
M_LockMutex(hLeakMutex)
ForEach NoLeak_ALLOC()
If NoLeak_ALLOC()\iAddress = iAddress
DeleteElement(NoLeak_ALLOC())
Break
EndIf
Next
AddElement(NoLeak_ALLOC())
NoLeak_ALLOC()\Source$ = Source$
NoLeak_ALLOC()\iLine = iLine
NoLeak_ALLOC()\iBytes = iBytes
NoLeak_ALLOC()\iAddress = iRetVal
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro ReAllocateMemory (iAddress, iBytes, iFlags = 0)
_ReAllocateMemory (#PB_Compiler_File, #PB_Compiler_Line, iAddress, iBytes, iFlags)
EndMacro
;}
;{ FreeMemory()
Procedure _FreeMemory (iAddress)
Shared NoLeak_ALLOC()
M_LockMutex(hLeakMutex)
ForEach NoLeak_ALLOC()
If NoLeak_ALLOC()\iAddress = iAddress
DeleteElement(NoLeak_ALLOC())
Break
EndIf
Next
M_UnlockMutex(hLeakMutex)
FreeMemory(iAddress)
EndProcedure
Macro FreeMemory (iAddress)
_FreeMemory (iAddress)
EndMacro
;}
;{ FreeImage()
Procedure _FreeImage (iImageNum, iOverWritten = 0)
Shared NoLeak_IMAGE()
M_LockMutex(hLeakMutex)
ForEach NoLeak_IMAGE()
If NoLeak_IMAGE()\iImageNum = iImageNum
DeleteElement(NoLeak_IMAGE())
Break
EndIf
Next
M_UnlockMutex(hLeakMutex)
If iOverWritten = 0
FreeImage(iImageNum)
EndIf
EndProcedure
Macro FreeImage (iImageNum)
_FreeImage (iImageNum)
EndMacro
;}
;{ LoadImage()
Procedure.i _LoadImage (Source$, iLine, iImageNum, Filename$, iFlags)
Shared NoLeak_IMAGE()
Protected iRetVal, iRetImage
iRetVal = LoadImage (iImageNum, Filename$, iFlags)
If iRetVal
If iImageNum <> #PB_Any
_FreeImage (iImageNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_IMAGE())
NoLeak_IMAGE()\Source$ = Source$
NoLeak_IMAGE()\iLine = iLine
If iImageNum = #PB_Any
iRetImage = iRetVal
Else
iRetImage = iImageNum
EndIf
NoLeak_IMAGE()\iImageNum = iRetImage
NoLeak_IMAGE()\iWidth = ImageWidth(iRetImage)
NoLeak_IMAGE()\iHeight = ImageHeight(iRetImage)
NoLeak_IMAGE()\iDepth = ImageDepth(iRetImage)
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro LoadImage (iImageNum, Filename, iFlags = 0)
_LoadImage (#PB_Compiler_File, #PB_Compiler_Line, iImageNum, Filename, iFlags)
EndMacro
;}
;{ GrabImage()
Procedure.i _GrabImage (Source$, iLine, iImageSrc, iImageNum, iX, iY, iWidth, iHeight)
Shared NoLeak_IMAGE()
Protected iRetVal, iRetImage
iRetVal = GrabImage (iImageSrc, iImageNum, iX, iY, iWidth, iHeight)
If iRetVal
If iImageNum <> #PB_Any
_FreeImage (iImageNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_IMAGE())
NoLeak_IMAGE()\Source$ = Source$
NoLeak_IMAGE()\iLine = iLine
If iImageNum = #PB_Any
iRetImage = iRetVal
Else
iRetImage = iImageNum
EndIf
NoLeak_IMAGE()\iImageNum = iRetImage
NoLeak_IMAGE()\iWidth = ImageWidth(iRetImage)
NoLeak_IMAGE()\iHeight = ImageHeight(iRetImage)
NoLeak_IMAGE()\iDepth = ImageDepth(iRetImage)
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro GrabImage (iImageSrc, iImageNum, iX, iY, iWidth, iHeight)
_GrabImage (#PB_Compiler_File, #PB_Compiler_Line, iImageSrc, iImageNum, iX, iY, iWidth, iHeight)
EndMacro
;}
;{ CopyImage()
Procedure.i _CopyImage (Source$, iLine, iImageSrc, iImageNum)
Shared NoLeak_IMAGE()
Protected iRetVal, iRetImage
iRetVal = CopyImage (iImageSrc, iImageNum)
If iRetVal
If iImageNum <> #PB_Any
_FreeImage (iImageNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_IMAGE())
NoLeak_IMAGE()\Source$ = Source$
NoLeak_IMAGE()\iLine = iLine
If iImageNum = #PB_Any
iRetImage = iRetVal
Else
iRetImage = iImageNum
EndIf
NoLeak_IMAGE()\iImageNum = iRetImage
NoLeak_IMAGE()\iWidth = ImageWidth(iRetImage)
NoLeak_IMAGE()\iHeight = ImageHeight(iRetImage)
NoLeak_IMAGE()\iDepth = ImageDepth(iRetImage)
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro CopyImage (iImageSrc, iImageNum)
_CopyImage (#PB_Compiler_File, #PB_Compiler_Line, iImageSrc, iImageNum)
EndMacro
;}
;{ CatchImage()
Procedure.i _CatchImage (Source$, iLine, iImageNum, iMemoryAddress, iSize)
Shared NoLeak_IMAGE()
Protected iRetVal, iRetImage
iRetVal = CatchImage (iImageNum, iMemoryAddress, iSize)
If iRetVal
If iImageNum <> #PB_Any
_FreeImage (iImageNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_IMAGE())
NoLeak_IMAGE()\Source$ = Source$
NoLeak_IMAGE()\iLine = iLine
If iImageNum = #PB_Any
iRetImage = iRetVal
Else
iRetImage = iImageNum
EndIf
NoLeak_IMAGE()\iImageNum = iRetImage
NoLeak_IMAGE()\iWidth = ImageWidth(iRetImage)
NoLeak_IMAGE()\iHeight = ImageHeight(iRetImage)
NoLeak_IMAGE()\iDepth = ImageDepth(iRetImage)
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro CatchImage (iImageNum, iMemoryAddress, iSize = $7FFFFFFF)
_CatchImage (#PB_Compiler_File, #PB_Compiler_Line, iImageNum, iMemoryAddress, iSize)
EndMacro
;}
;{ CreateImage()
Procedure.i _CreateImage (Source$, iLine, iImageNum, iWidth, iHeight, iDepth, iBackColor)
Shared NoLeak_IMAGE()
Protected iRetVal
iRetVal = CreateImage (iImageNum, iWidth, iHeight, iDepth, iBackColor)
If iRetVal
If iImageNum <> #PB_Any
_FreeImage (iImageNum, 1)
EndIf
M_LockMutex(hLeakMutex)
AddElement(NoLeak_IMAGE())
NoLeak_IMAGE()\Source$ = Source$
NoLeak_IMAGE()\iLine = iLine
NoLeak_IMAGE()\iWidth = iWidth
NoLeak_IMAGE()\iHeight = iHeight
NoLeak_IMAGE()\iDepth = iDepth
If iImageNum = #PB_Any
NoLeak_IMAGE()\iImageNum = iRetVal
Else
NoLeak_IMAGE()\iImageNum = iImageNum
EndIf
M_UnlockMutex(hLeakMutex)
EndIf
ProcedureReturn iRetVal
EndProcedure
Macro CreateImage (iImageNum, iWidth, iHeight, iDepth = 24, iBackColor = RGB(0,0,0))
_CreateImage (#PB_Compiler_File, #PB_Compiler_Line, iImageNum, iWidth, iHeight, iDepth, iBackColor)
EndMacro
;}
CompilerEndIf
TEST PROGRAM
Code: Select all
IncludeFile "NoLeak.pb"
Procedure BadAllocation()
Protected *ptr
*ptr = AllocateMemory(1234, #PB_Memory_NoClear)
; overwrite same pointer
*ptr = AllocateMemory(5678)
; this is bad, we lose the previous one
FreeMemory(*ptr)
; this should sooner or later fail -> (*ptr = 0) so no leakage on this line
; if/when does not fails it causes as a second leakage
*ptr = AllocateMemory($7FFFFFFF)
EndProcedure
Procedure GoodAllocation()
Protected *ptr
*ptr = AllocateMemory(4096)
; reuse same pointer
*ptr = ReAllocateMemory(*ptr, 8192)
; all ok
FreeMemory(*ptr)
EndProcedure
Procedure BadImageCreation()
Protected img
; this is ok
img = CreateImage(#PB_Any, 128, 128)
FreeImage(img)
; this is freed by the one who follow
CreateImage(1, 640, 480, 32)
; this is not freed right now but it will be in the next call of this procedure by the command above ...
; ... until the last loop, then it will leak once
CreateImage(1, 320, 240)
; this is also not freed
img = CreateImage(#PB_Any, 640, 480, 32)
; this is not either
img = CopyImage(img, #PB_Any)
EndProcedure
Procedure GoodImageCreation()
Protected img, img2
; this is ok
img = CreateImage(#PB_Any, 128, 128)
img2 = GrabImage(img, #PB_Any, 0, 0, 32, 32)
FreeImage(img)
FreeImage(img2)
EndProcedure
Procedure Main()
Protected img, k
; this is formally bad without a free, but it's acceptable
img = CreateImage(0, 320, 200)
For k = 1 To 3
; leakages inside functions called many times should be fixed
; especially if the "many times" is not known in advance !
BadAllocation()
GoodAllocation()
BadImageCreation()
GoodImageCreation()
Next
EndProcedure
Main()
ShowVariableViewer()
CallDebugger
EDIT : changed the example to remove the "F12 thing" causing some confusion, now we can use ShowVariableViewer()
EDIT : updated as requested to PB 5.2
If you prefer to dump the infos directly to the debug window you can obviously loop through the list with some code like this: http://www.purebasic.fr/english/viewtop ... 63#p424463