5. Data storage¶
This chapter discusses controlling data placement and its impact on application efficiency.
The 6502 features a 64K address range for code and data. A key feature is the zero page, the first 256 bytes of memory, which the 6502 can address more quickly and efficiently than arbitrary addresses within the full 64K memory space.
Since the 6502 has only three single-byte registers, it quickly needs to use external resources for data storage, traditionally the stack. However, due to the small stack size and lack of efficient access to it, the C compiler allocates a 48-byte fast-access register area in the zero page. This area is evenly divided into two parts: one that survives a function call and one that does not.
Consequently, the compiler has ample fast pseudo-registers and rarely needs to allocate auto variables on the stack. This results in excellent performance, as most auto variable accesses occur in the fast zero page. All functions are also fully reentrant.
The downside is that some functions need to call a small runtime library routine to move data between the register area and the data stack to provide reentrant code.
This data stack is separate from the 6502 small hardware stack and can be as large as addressable memory (64K). However, you are still limited by the hardware stack for function return addresses, permitting function call nesting to a depth of about 100 levels.
5.1. Ways to store data¶
Data can be allocated as auto, static, or on the heap. Variable scope and the need for allocation during runtime determine placement.
As a general rule, use auto-allocated variables whenever possible. This offers the compiler maximum flexibility for resource allocation closest to the core, which typically results in the most efficient data access.
Auto variables¶
Auto variables include function parameters and local variables not
defined with static. The compiler attempts to allocate these in
processor registers or zero page pseudo registers; otherwise, the stack is used.
Auto variables are allocated only when used. Their registers can be reused for other auto variables or temporary data once no longer needed. This reuse also applies to stack locations.
Auto variables can have multiple live ranges; a variable with the same name might be used in distinct parts of a function. Internally, these are treated as different variables and may be allocated to various locations, potentially not existing between live ranges.
All auto variables associated with a function are deallocated upon function exit.
Note
If you take the address of an auto variable, you can pass its pointer to other functions, which is useful for temporary storage. However, avoid using such pointers outside their scope, as auto variables are deallocated on function exit. Be aware that taking an address allocates the variable on the stack, potentially increasing access cost compared to other auto variables.
Static variables¶
Global, module, or function static variables are allocated in global memory, occupying space for the application’s duration.
Their visibility varies. A global variable is universally visible but
requires extern for use. A module static variable, declared with
static at file scope, is visible within one compilation unit.
A static local variable within a function’s scope is only visible
there, retaining its value across function calls. Use static to
differentiate it from an auto variable.
Dynamically allocated¶
A dynamically allocated variable is retrieved from a heap using the malloc function. This is useful when the required data size is unknown at program startup.
Note
Dynamically allocated variables are a potential problem in memory constrained systems if the program is left running for a long time due to heap fragmentation.
5.2. Address spaces¶
The compiler provides multiple address spaces, which are addressable memory areas with specific properties:
Address width for pointers
Width of the associated integral index type
Different instruction sequences for accessing various address spaces
An extension keyword or type attribute name
Section names tied to the address space for linking control
Address space attributes are always active in the compiler.
The Calypsi C compiler tool chain provides two address spaces, zero page and the default 16 bits memory that can point to any address in the full 64K range.
All unqualified pointers are 16 bits wide. There is no keyword or address space attribute provided to explicitly describe them.
Zero page¶
The zero page address space is the lowest 256 bytes of memory, using
the address range 0x00-0xff.
A zero page pointer is 8 bits wide and occupies a single byte when
stored in memory.
As it is only 256 bytes and shared with pseudo-registers, storage in the zero page is somewhat limited. However, careful use of the zero page can produce a more efficient application.
Default memory¶
The default memory address space is the full 64K address range
0x0000-0xffff. A default memory pointer is 16 bits and occupies two
bytes when stored to memory.
Far memory¶
The Far memory address space addresses the full memory range beyond 64K and is available on the MEGA65 target.
Huge memory¶
The Huge memory address space addresses the full memory range beyond 64K and is available on the MEGA65 target.
The size of a data object is limited only by available memory.
Summary¶
The following table summarizes the available address spaces.
Memory type |
Keyword |
Address range |
Pointer size |
Index type |
|---|---|---|---|---|
Zero page |
|
|
8 bits |
|
Default |
N/A |
|
16 bits |
|
When the 45GS02 for the MEGA65 is enabled, the following additional data pointers are available:
Memory type |
Keyword |
Address range |
Pointer size |
Index type |
|---|---|---|---|---|
Far |
|
|
32 bits |
|
Huge |
|
|
32 bits |
|
Syntax¶
An address space attribute keyword such as __zpage is
a type qualifier. Syntactically it works the same as other C language
defined type qualifier, e.g. const and volatile.
The following declaration defines four variables in the Zero page address space:
__zpage int a, b;
int __zpage c, d;
The __zpage type qualifier applies to the closest type,
int in this example. In C, the order between type qualifiers and
types does not matter; they convey the same meaning.
Pointers¶
A pointer in C points to something in memory. Both the pointer itself
and what it points to have types. As an example, the type char *
is a pointer to a char.
Pointer types are easier to understand if you read them from right to
left. The * is a pointer, so char * reads from right to left
as “pointer to char”. This order of reading is especially useful when
you mix in type qualifiers in pointer types, as it makes it a lot
easier to read and understand what the type means.
int __attribute__((zpage)) * p1;
long * __attribute__((zpage)) p2;
Here, p1 is a pointer stored in default memory that points to an
int in Zero page memory. p2 is a pointer stored
in Zero page memory that points to a long in default
memory.
Structures¶
You can place a structure in a specified address space. This means that all its members are in that address space. You cannot override individual structure members using an address space keyword. It is however possible to have members of the structure that point to a different address space.
struct tag {
int __zpage * p;
int value;
};
struct tag __zpage myTag;
This is however not allowed:
struct tag {
int * __zpage p; /* incorrect */
int __zpage value; /* incorrect */
};