6.16 Named Address Spaces

As an extension, GNU C supports named address spaces as defined in the N1275 draft of ISO/IEC DTR 18037. Support for named address spaces in GCC will evolve as the draft technical report changes. Calling conventions for any target might also change. At present, only the AVR, SPU, M32C, RL78, IA-16, and x86 targets support address spaces other than the generic address space.

Address space identifiers may be used exactly like any other C type qualifier (e.g., const or volatile). See the N1275 document for more details.

6.16.1 AVR Named Address Spaces

On the AVR target, there are several address spaces that can be used in order to put read-only data into the flash memory and access that data by means of the special instructions LPM or ELPM needed to read from flash.

Per default, any data including read-only data is located in RAM (the generic address space) so that non-generic address spaces are needed to locate read-only data in flash memory and to generate the right instructions to access this data without using (inline) assembler code.

__flash

The __flash qualifier locates data in the .progmem.data section. Data is read using the LPM instruction. Pointers to this address space are 16 bits wide.

__flash1
__flash2
__flash3
__flash4
__flash5

These are 16-bit address spaces locating data in section .progmemN.data where N refers to address space __flashN. The compiler sets the RAMPZ segment register appropriately before reading data by means of the ELPM instruction.

__memx

This is a 24-bit address space that linearizes flash and RAM: If the high bit of the address is set, data is read from RAM using the lower two bytes as RAM address. If the high bit of the address is clear, data is read from flash with RAMPZ set according to the high byte of the address. See __builtin_avr_flash_segment.

Objects in this address space are located in .progmemx.data.

Example

char my_read (const __flash char ** p)
{
    /* p is a pointer to RAM that points to a pointer to flash.
       The first indirection of p reads that flash pointer
       from RAM and the second indirection reads a char from this
       flash address.  */

    return **p;
}

/* Locate array[] in flash memory */
const __flash int array[] = { 3, 5, 7, 11, 13, 17, 19 };

int i = 1;

int main (void)
{
   /* Return 17 by reading from flash memory */
   return array[array[i]];
}

For each named address space supported by avr-gcc there is an equally named but uppercase built-in macro defined. The purpose is to facilitate testing if respective address space support is available or not:

#ifdef __FLASH
const __flash int var = 1;

int read_var (void)
{
    return var;
}
#else
#include <avr/pgmspace.h> /* From AVR-LibC */

const int var PROGMEM = 1;

int read_var (void)
{
    return (int) pgm_read_word (&var);
}
#endif /* __FLASH */

Notice that attribute progmem locates data in flash but accesses to these data read from generic address space, i.e. from RAM, so that you need special accessors like pgm_read_byte from AVR-LibC together with attribute progmem.

Limitations and caveats

6.16.2 IA-16 Named Address Spaces

6.16.2.1 __far

On the IA-16 target, a pointer qualified with the word __far is a 32-bit “far pointer” which can be used to access memory outside of the 64 KiB generic address space.

One way to create a far pointer value is to cast a 32-bit integer value to a far pointer type. The value’s upper 16 bits will be the segment, and the lower 16 bits the offset.

unsigned long irq0_clock_ticks (void)
{
    /* Read the 32-bit BIOS variable at segment:offset ==
       0x0040:0x006C.  */
    volatile unsigned long __far *p =
        (volatile unsigned long __far *) 0x0040006C;
    return *p;
}

Another way is to cast a generic data pointer, __seg_ss data pointer (see below), or function pointer, to a far pointer type. For a data pointer, the resulting pointer will have the data segment value—taken from the ds or ss register—as its segment component; for a function pointer, this will be from cs.

void set_diskette_params (const unsigned char params[])
{
    /* Set the BIOS diskette parameters (int $0x1e vector).  */
    * (const unsigned char __far * volatile __far *)
        (4 * 0x1e) =
            (const unsigned char __far *) params;
}

Far Static Storage Variables

Under the small memory model (-mcmodel=small)—and unless you specify -mno-segment-relocation-stuff—you can also define far variables with static storage duration, and take their addresses, like so:

int __far x;
int __far *f (int b)
{
    static int __far y = 1;
    return b ? &x : &y;
}

Far Functions

There is some preliminary support for defining and invoking far functions, as well as defining far function pointers. To mark a function as far, one way is to write the word __far immediately after its parameter list. For example:

int (*foo) (long) __far;
int bar (void) __far
{
    return foo (3L) + 1;
}

You can also declare or define a far function using a syntax more compatible with that of classical IA-16 compilers:

int __far baz (void)
{
    return 2;
}
int __far qux (long);
int __far (*quux) (int);

By default, GCC treats far functions similarly to the Open Watcom C/C++ compiler:

  • When a module defines a far function, GCC places it in the program’s text segment.
  • If a module calls a far function which is defined in the same module, GCC can tell that the caller and callee reside in the same IA-16 segment. GCC will output a ‘pushw %cs; call’ or ‘jmp’ instruction sequence to realize the call.
  • However, if a module calls a far function which is defined elsewhere—or if the call is done through a pointer—GCC assumes nothing about where the function is. GCC will use a far lcall or ljmp, possibly with a segment relocation, to realize the call.

If you know that an externally defined far function is guaranteed to be in the program’s text segment, you can get GCC to use call or jmp for this function, with an attribute:

int baz (long) __far __attribute__ ((__near_section__));

Conversely, if you would like to place a locally defined far function outside the normal text segment, you can use a different attribute:

__attribute__ ((__far_section__))
__far int qux (long x)
{ ... }

It is now (April 2019) possible for far_section functions to call non-far functions in the normal text segment—including standard C library functions and compiler support routines. However, the internal implementation for such calls is still rather inefficient.

Limitations and Caveats

  • If a pointer arithmetic operation causes the pointer’s offset portion to wrap around—from 0xFFFF to 0x0000, or vice versa—the resulting pointer is undefined. This may change in the future.
  • To avoid ambiguity in parsing, the postfix __far notation above for declaring far functions is not allowed with K&R-style function definitions.
  • Starting from August 2019, a declaration like
    static int __far x, y, z;
    

    guarantees that the variables x, y, and z will be placed in the same far segment—though not necessarily in that order. (To lay out a set of far variables in a fixed order, you can consider wrapping them in a struct.)

    GCC also guarantees, given an appropriate linker script at link time, that x, y, and z will be placed outside the generic (near) data address space.

  • When using the (default) tiny memory model, or if -mno-segment-relocation-stuff is in effect, operations which require the output code to contain segment relocations are not allowed.

    In particular, you cannot place a static storage variable in the __far space, or invoke an externally defined far function by name (unless it is marked near_section).

  • For far pointer declarations, the GCC named address space syntax—which follows N1275—differs somewhat from the syntax used in classical DOS compilers such as Open Watcom C/C++ and Borland’s Turbo C. The code
    char __far *p, *q;
    

    declares p and q as far pointers under GCC, but under Open Watcom it declares only p as a far pointer, and q as a generic pointer.

    If -Waddress (or -Wall) is enabled, GCC will warn about cases like the above, where a declaration can be interpreted differently under GCC’s grammar and Watcom-style grammar.

    Also note that, unlike Open Watcom, GCC does not support the

    int (__far *foo) (long);
    

    syntax for declaring far function pointers.

  • The option -ffixed-ds does not currently interact well with some far address operations—such as copying a struct between two far addresses. This, too, may change in the future.

6.16.2.2 __seg_ss

Within a function marked __attribute__ ((__no_assume_ss_data__)) (see IA-16 Function Attributes), a pointer qualified with __seg_ss can be used to refer to memory in the caller’s stack, rather than the program’s data segment.

6.16.2.3 Preprocessor symbols

See IA-16 Built-in Macros.

6.16.3 M32C Named Address Spaces

On the M32C target, with the R8C and M16C CPU variants, variables qualified with __far are accessed using 32-bit addresses in order to access memory beyond the first 64 Ki bytes. If __far is used with the M32CM or M32C CPU variants, it has no effect.

6.16.4 RL78 Named Address Spaces

On the RL78 target, variables qualified with __far are accessed with 32-bit pointers (20-bit addresses) rather than the default 16-bit addresses. Non-far variables are assumed to appear in the topmost 64 KiB of the address space.

6.16.5 SPU Named Address Spaces

On the SPU target variables may be declared as belonging to another address space by qualifying the type with the __ea address space identifier:

extern int __ea i;

The compiler generates special code to access the variable i. It may use runtime library support, or generate special machine instructions to access that address space.

6.16.6 x86 Named Address Spaces

On the x86 target, variables may be declared as being relative to the %fs or %gs segments.

__seg_fs
__seg_gs

The object is accessed with the respective segment override prefix.

The respective segment base must be set via some method specific to the operating system. Rather than require an expensive system call to retrieve the segment base, these address spaces are not considered to be subspaces of the generic (flat) address space. This means that explicit casts are required to convert pointers between these address spaces and the generic address space. In practice the application should cast to uintptr_t and apply the segment base offset that it installed previously.

The preprocessor symbols __SEG_FS and __SEG_GS are defined when these address spaces are supported.