Stack size and sub-methods

how are variables allocated in the heap space?

The heap is the area managed by malloc(), so it doesn't get "variables" per se.
RAM is divided into 4 regions:

  • Data: initialized data. The compiler/linker can keep track of exactly how much there is of this, based on just the source code.
  • BSS: uninitialized data. Ditto, except that it doesn't need a copy stored in flash to do the initialization. Data and BSS will normally be positioned starting at the very beginning of the actual RAM, at least on microcontrollers where the code is "elsewhere."
  • Stack: Usually starts at the top of memory and grows downward. The stack gets return addresses for subroutines, saved registers, and "local variables" (those defined inside of a function.)
  • Heap: memory that can be dynamically allocated, usually by functions like malloc() and free() (though I suppose "new" as well?) Usually the Heap will start immediately after the Data and BSS areas, and grows "upward" until it collides with the stack (hopefully that doesn't happen.) Details of how the heap is managed are up to the library functions. The compiler/linker will usually output enough information that C-level code will know "there is a region of memory starting at X and ending sometime before Y that is not in use by any of the variables defined in the program." Then it can carve that up using a bunch of different possible algorithms (with varying memory overhead, speed of malloc or free, faster access to commonly used bock sizes, coallescing of freed memory, reporting and debugging, and ... other features.) (It is my observation, in 20+y of experience with an in-house-written malloc(), that the memory allocator will gradually increase in complexity until the point where someone decides that there needs to be a "low overhead" version that is faster and cheaper. The applications that want the extra speed start using that, and adding features to IT until it reaches a complexity that ...) (and I am utterly SHOCKED at the lack of debugging fetaures in most allocator implementations.)

You'll notice that the above "theory" is relatively independent of the actual language in use. Pretty much all modern computer languages will have the same basic regions of memory, treated the same way. A big difference is that some languages will have "garbage collection" that will try to figure out when dynamically allocated memory is no longer being used, removing the need for the programmer to explicitly free memory. (which is nicer when you have a gig or two of free memory, so that having a bunch sitting around waiting to be GC'ed doesn't stop the rest of your program from working.)