5. Data storage

This chapter discusses controlling data placement and its impact on application efficiency.

The 68000 is a 32 bits architecture that can address a linear 4GB address range which is shared by code and data.

As there is a good amount of storage registers and your statically allocated data is probably not more than 64K, you can normally use the Small data model which reserves one register as a base address for addressing static data. This typically saves two bytes of program space for every instruction that access static data.

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; 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 for the 68000 provides two address spaces, near and far.

All pointers to data memory are 32 bits wide and occupy 4 bytes of memory when stored in memory. The keywords and address space attributes are intended to describe a storage location to help placement in appropriate sections.

Near address space

The near address space is a memory area reachable from a base address held in register A4. This can be used in either the Small or Large data models, but it is not available in the Far-only data model.

You can have a maximum of 64K data in the near address space. If you run out of space you may select a couple of larger data objects and move them to the far address space (see below). 64K can be considered quite a lot of memory for storing reasonable small static data objects. Stack and heap allocated objects are allocated outside this 64K memory range.

Far address space

The far address space makes use of the full 32 bit address range. The amount of static data objects can be up to 4GB. Direct access to the far address space costs an additional two extra bytes for each instruction compared to using the near address space.

Note

While doing a direct access to a far object costs two extra bytes, there is no difference when using a pointer to the near or the far address area, they have the same cost.

Summary

The following table summarizes the available address spaces.

Table 5.1 Address spaces

Memory type

Keyword

Address range

Pointer size

Index type

Near

__near

-0x8000 to 0x7fff

32 bits

int32_t

Far

__far

0x00000000 to 0xffffffff

32 bits

int32_t

Note

The Near memory type allocates data in a 64K bytes large area pointed to by a base pointer held in register A4. Pointers and address arithmetics are performed using 32 bit operations.

Syntax

An address space attribute keyword such as __far 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 Far memory address space:

__far int a, b;
int __far c, d;

The __far 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__((far)) * p1;
long * __attribute__((far)) p2;

Here, p1 is a pointer stored in default memory that points to an int in Far memory memory. p2 is a pointer stored in Far memory 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 __far * p;
  int value;
};

struct tag __far myTag;

This is however not allowed:

struct tag {
  int * __far p;     /* incorrect */
  int __far value;   /* incorrect */
};