Understanding memory (de)allocation

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Understanding memory (de)allocation

dsguy
Hi,
I am having a difficult time really understanding how memory management works in Jack.

A bit of context:
For my chapter 9 project I created a "Snake game" application. I added a replay loop, that, upon "death"/game over, prompts the user and then either re-starts with a fresh copy of a game instance (retry) or quits - based on the user's input.

In my first version, I ran into a "Heap Overflow" after multiple "retries". The reason was that my implementation of the snake is very "dumb". I am allocating two arrays (class field variables) of the max possible snake length that will hold the x and y coordinates. These are class variables of the Snake.jack class and initially I did not deallocate them manually.

A couple of things I do not understand:
Q1: Why are the "x" and "y" arrays from above not deAllocated automatically when the Snake object instance is being disposed?
Q2: Why do Jack integers & booleans not have to be deAllocated? From my understanding, there really is no difference between an Array or an Integer other than the former also has an offset to a base address.
Q3: Shouldn't my program - theoretically - produce a heap overflow of not deAllocated Integer & Boolean variables, even if it is after thousands of games?

Thanks!
Reply | Threaded
Open this post in threaded view
|

Re: Understanding memory (de)allocation

WBahn
Administrator
A1) Like the C programming language, Jack has no automatic garbage collection (unlike languages such as Java and Python). So the only way for memory to be freed is for your program to deallocate the memory with deAlloc(). Standard practice is for your class to have a dispose() method. This method needs to be responsible for not just freeing the memory of the object itself, but also any objects, such as your x andy arrays, that are part of the object.

A2) The only variables that are stored on the heap are ones that are explicitly allocated memory there using alloc(). Other variables are either stored in the static memory area or on the stack. Variables stored on the stack are stored in a function's stack frame and are therefore automatically allocated and deallocated as part of the function calling and return routines. There is a fundamental difference between Integer and Array types -- the Array type is an object that has memory allocated on the heap for it when it is created. The Integer is a primitive type that, as noted above, is allocated memory either in the area reserved for static variables or on the stack.

A3) See above.

Even if you do all of this, you may still have heap overflows eventually because Jack does not treat string literals well. Memory gets allocated for them by the compiler, but there is not way to deallocate them when finished. This results in a "memory leak" that is undesirable. The way that most compilers deal with this is allocating memory for string literals as part of the code itself and then replacing references to the string with the associated pointers. Something similar could be done in Jack, although putting the strings on the Heap (don't want to chew up the limited static area with them). But then the compiler has to be a bit more complex and the authors chose to accept the memory leak instead of burdening the students with the added complexity -- the compiler is already challenging enough.
Reply | Threaded
Open this post in threaded view
|

Re: Understanding memory (de)allocation

dsguy
Thanks for the quick reply, that helps a lot!

This fundamental difference between primitives and Array types, did I miss it in chapter 9 or will this be explained in the compiler chapters?

Also, is this "standard" behaviour? I.e. do C compilers also allocate primitives onto the stack?
Reply | Threaded
Open this post in threaded view
|

Re: Understanding memory (de)allocation

WBahn
Administrator
If you've done the VM translator, then the mechanism by which static variables are handled and how local variables are dealt with on the stack are covered. The THIS and THAT pointers are for accessing objects on the heap.

More of the detail will be dealt with as you write the compiler.

Actual C compilers have a lot of flexibility in how they handle things. The C language standard itself never mentions the notion of a stack or a heap (neither word appears any where in the many, many hundreds of pages of the standard). Instead, the standard describes required behavior and the compiler implementer is allowed to comply with that behavior in any way they see fit.

But the required behavior is generally consistent with the notion of variables that are dynamically allocated being placed in the heap and all other variables being placed in static memory or in automatic memory on a stack. When targeting processors that have a lot of registers, most compilers put local variables into the registers as much as possible, instead of on the stack.

It really doesn't matter whether the variable is a primitive type or a user-defined type, what matters is whether or not the program allocated memory for it dynamically.
Reply | Threaded
Open this post in threaded view
|

Re: Understanding memory (de)allocation

dsguy
Very helpful, thanks. Much appreciated!