Memory Leaks in 2.0+

Level 11
Joined
Jul 28, 2024
Messages
340
Code:
function ApplyDamage takes nothing returns nothing
  local group gkGroup
  local unit gk
  set gkGroup = GetAllUnitsByTypeId('njks')
  loop
    set gk = FirstOfGroup(gkGroup)
    exitwhen gk == null
       
    call IssueImmediateOrder(gk, "holdposition")
    call GroupRemoveUnit(gkGroup, gk)
  endloop
   
  call DestroyGroup(gkGroup)
       
  call DistributeDamage(2110, guNgate, guSgate)
endfunction

I dont set unit to null here.
Will it cause a memory leak? Intuition tells me that it will not. But what information do you have?
 
Nulling local variables is used to prevent reference leaks. When you create a game object in wc3, there is some number in memory somewhere that is tracking how many references there are to that object. When a variable is assigned to it, that counter goes up by 1. When a variable no longer points to it (e.g. by assigning it to another object or to null) then that counter goes down by 1. The key thing to note is that this reference counter does not prevent the object from being destroyed, it is simply used to track whether its handle ID is now free to be used for future objects.

This is an important distinction--because reference leaks are a much smaller memory footprint compared to an object leak (i.e. an object leak is where you forget to do RemoveLocation / DestroyGroup etc. on objects you no longer need).

The reason we assign variables to null in JASS is because Warcraft 3 doesn't decrement that reference count when a local variable goes "out of scope" (i.e. when you leave that function scope via endfunction or return). This means the reference count will never be able to reach 0 and thus that handle ID will permanently be reserved. That doesn't mean the object can't be removed--it just means that ID can't be re-used so that reference counter memory will stay there. You can do a simple test in a map to verify this:

Without Null

With Null


JASS:
function Trig_No_Null_Actions takes nothing returns nothing
    local group g = CreateGroup()
    call BJDebugMsg(I2S(GetHandleId(g))) // this counter will keep going up since the handle IDs are never marked as "free"
    call DestroyGroup(g)
endfunction

//===========================================================================
function InitTrig_No_Null takes nothing returns nothing
    set gg_trg_No_Null = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_No_Null, Player(0), "-no-null", true )
    call TriggerAddAction( gg_trg_No_Null, function Trig_No_Null_Actions )
endfunction
JASS:
function Trig_Null_Actions takes nothing returns nothing
    local group g = CreateGroup()
    call BJDebugMsg(I2S(GetHandleId(g))) // you'll see this cycling between the same two handle IDs, since those IDs are correctly "freed"
    call DestroyGroup(g)
    set g = null
endfunction

//===========================================================================
function InitTrig_Null takes nothing returns nothing
    set gg_trg_Null = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_Null, Player(0), "-null", true )
    call TriggerAddAction( gg_trg_Null, function Trig_Null_Actions )
endfunction


It is also important to note that a single counter is stored per ID, not per variable. So that means that if you have 1000 local variables pointing to some single footman and you didn't null them, that would still just be 1 reference leak (not 1000).

So yes, this issue still exists in reforged for JASS (not Lua, that has a difference garbage collection/handle ID management system). But is it significant? Not as much anymore. We used to care more about it because we'd use the handle IDs (minus 0x100000) as indices in arrays for data storage (before hashtables were added), so it was really important to keep the handle ID count less than 8191 (the array index limit) or else your arrays could crash the game. I personally still null my variables in JASS out of habit and because it can still be useful for using handle counters to see if you have leaks in your map.



In your case, you do not have a reference leak for gk because your loop exits when gk == null. So by the time it reaches the end of the function, gk is pointing to null.

However, gkGroup will lead to a reference leak since it is still pointing to an object by the time the function completes. Up to you whether you want to null it or not based on the information above. :thumbs_up:
 
Back
Top