cadillackid wrote:so the fact that I use a lot of variables could be a reason why I use so much space for such a small program
To get a grasp on how RAM is used, you need to understand how RAM is partitioned and how space is allocated for different types of variables.
Consider the diagram below (excerpted from the
ZBasic Language Reference Manual). The top portion represents the data items that are statically allocated, meaning that they are defined at the module level, i.e. defined outside of any procedure, or defined within a procedure with the Static attribute. The variables in this space exist for the entire time that the application is running. Note that the stacks for the tasks other than Main() are generally statically allocated so they "live" in this space, too.
The middle portion of RAM is dedicated to the task stack for Main(). (As a side note, the task stacks are used beginning from low end for VM devices but they are used beginning from the high end for native devices.) When you define a variable within a procedure (and it doesn't have the Static attribute), space for that variable is allocated on the calling task's stack. The space is "in use" only for the duration of the execution of that procedure and it re-used when the next procedure is invoked. Note, however, that if A calls B which calls C, the local variables for all three procedures exist on the stack while C is executing. When the compiler performs stack use analysis, it takes these details into consideration.
The bottom portion of the diagram is the heap (which grows from the end of RAM toward lower addresses). As described in an earlier post, the heap is used primarily for storing the characters of run-time computed strings. It is also used by System.Alloc() and when writing ProgMem variables on ZX devices which use internal Flash memory for program storage (i.e. they do
not use an external EEPROM).
As described earlier, in a VM mode device, the Main() task stack grows from lower address toward higher addresses and the heap grows from higher addresses toward lower addresses. This fact allows the Main() task stack and the heap to share the space above the statically allocated section. As long as neither grows too large, the program will execute without problem.
In contrast, in a native mode device the Main() task stack grows from higher addresses toward lower addresses. This is an artifact of the way that the hardware stack is implemented in the AVR devices (and most other microcontrollers, too) and it cannot be changed. This fact means that the heap must be given a specific size when the program begins execution. By default, the heap is 512 bytes meaning that the Main() task stack begins 512 bytes from the end of RAM. Depending on your application, this may be more or less than is actually needed. If it is more than is needed RAM is "wasted" and if it is less than needed the application won't run correctly (the primary symptom will be that some string values are zero-length when they shouldn't be).
There are some files produced during compiling/linking that will help you understand how your RAM is being used. For VM mode devices, you can add the --map option to the .pjt file. The content of this file will show all statically allocated variables, the stack use of all procedures and the minimum stack requirement for all tasks.
For native mode devices, the information is in two places. The back-end linker produces a .sym file that shows the address assignment of all procedures and statically allocated variables. If the --map option is present, the .map file will contain information about the stack use of all procedures and the minimum stack requirement for all tasks.