10.10 Variable Attributes

The MPLAB XC16 C Compiler uses attributes to indicate memory allocation, type and other configuration for variables, structure members and types. Other attributes are available for functions, and these are described in the Function Attributes section. Qualifiers are used independently to attributes (see the Compiler-Specific Type Qualifiers section). They only indicate how objects are accessed, but must be used where necessary to ensure correct code operation.

The compiler keyword __attribute__ allows you to specify the attributes of objects. This keyword is followed by an attribute specification inside double parentheses. The attributes below are currently supported for variables.

You may also specify attributes with __ (double underscore) preceding and following each keyword (e.g., __aligned__ instead of aligned). This allows you to use them in header files without being concerned about a possible macro of the same name.

To specify multiple attributes, separate them by commas within the double parentheses, for example:

__attribute__ ((aligned (16), packed)).

Note: It is important to use variable attributes consistently throughout a project. For example, if a variable is defined in file A with the far attribute and declared extern in file B without far, then a link error may result.

address (addr)

The address attribute specifies an absolute address for the variable. This attribute can be used in conjunction with a section attribute. This can be used to start a group of variables at a specific address:

int foo __attribute__((section("mysection"),address(0x900)));
int bar __attribute__((section("mysection")));
int baz __attribute__((section("mysection")));

A variable with the address attribute cannot be placed into the auto_psv space (see the space() attribute or the -mconst-in-code option); attempts to do so will cause a warning and the compiler will place the variable into the PSV space. If the variable is to be placed into a PSV section, the address should be a program memory address.

aligned (alignment)

This attribute specifies a minimum alignment for the variable, measured in bytes. The alignment must be a power of two. For example, the declaration:

int x __attribute__ ((aligned (16))) = 0;

causes the compiler to allocate the global variable x on a 16-byte boundary. On the dsPIC DSC device, this could be used in conjunction with an asm expression to access DSP instructions and addressing modes that require aligned operands.

As in the preceding example, you can explicitly specify the alignment (in bytes) that you wish the compiler to use for a given variable. Alternatively, you can leave out the alignment factor and just ask the compiler to align a variable to the maximum useful alignment for the dsPIC DSC device. For example, you could write:

short array[3] __attribute__ ((aligned));

Whenever you leave out the alignment factor in an aligned attribute specification, the compiler automatically sets the alignment for the declared variable to the largest alignment for any data type on the target machine – which in the case of the dsPIC DSC device is two bytes (one word).

The aligned attribute can only increase the alignment; you can decrease it by specifying packed (see below). The aligned attribute conflicts with the reverse attribute. It is an error condition to specify both.

The aligned attribute can be combined with the section attribute. This will allow the alignment to take place in a named section. By default, when no section is specified, the compiler will generate a unique section for the variable. This will provide the linker with the best opportunity for satisfying the alignment restriction without using internal padding that may happen if other definitions appear within the same aligned section.

boot

This attribute can be used to define protected variables in Boot Segment (BS) RAM:

int __attribute__((boot)) boot_dat[16];

Variables defined in BS RAM will not be initialized on startup. Therefore all variables in BS RAM must be initialized using inline code. A diagnostic will be reported if initial values are specified on a boot variable.

An example of initialization is as follows:

int __attribute__((boot)) time = 0; /* not supported */
int __attribute__((boot)) time2;
void __attribute__((boot)) foo()
{
  time2 = 55; /* initial value must be assigned explicitly */
}

deprecated

The deprecated attribute causes the declaration to which it is attached to be specially recognized by the compiler. When a deprecated function or variable is used, the compiler will emit a warning.

A deprecated definition is still defined and therefore present in any object file. For example, compiling the following file:

int __attribute__((__deprecated__)) i;
int main() {
  return i;
}

will produce the warning:

deprecated.c:4: warning: `i’ is deprecated (declared
 at deprecated.c:1)

i is still defined in the resulting object file in the normal way.

eds

In the attribute context, the eds (extended data space) attribute indicates to the compiler that the variable will be allocated anywhere within data memory. Variables with this attribute will likely also have the __eds__ type qualifier (see the Extended Data Space Access section) for the compiler to properly generate the correct access sequence. Not that the __eds__ qualifier and the eds attribute are closely related, but not identical. On some devices, eds may need to be specified when allocating variables into certain memory spaces such as space (ymemory) or space (dma) as this memory may only exist in the extended data space.

fillupper

This attribute can be used to specify the upper byte of a variable stored into a space(prog) section.

For example:

int foo[26] __attribute__((space(prog),fillupper(0x23))) = { 0xDEAD };

will fill the upper bytes of array foo with 0x23, instead of 0x00. foo[0] will still be initialized to 0xDEAD.

The command line option -mfillupper=0x23 will perform the same function.

far

The far attribute tells the compiler that the variable will not necessarily be allocated in near (first 8 KB) data space, (i.e., the variable can be located anywhere in data memory between 0x0000 and 0x7FFF).

mode (mode)

This attribute specifies the data type for the declaration as whichever type corresponds to the mode mode. This in effect lets you request an integer or floating point type according to its width. Valid values for mode are as follows:

ModeWidthCompiler Type
QI8 bitschar
HI16 bitsint
SI32 bitslong
DI64 bitslong long
SF32 bitsfloat
DF64 bitslong double

This attribute is useful for writing code that is portable across all supported compiler targets. For example, the following function adds two 32-bit signed integers and returns a 32-bit signed integer result:

typedef int __attribute__((__mode__(SI))) int32;
int32
add32(int32 a, int32 b)
  {
        return(a+b);
  }

You may also specify a mode of byte or __byte__ to indicate the mode corresponding to a one-byte integer, word or __word__ for the mode of a one-word integer, and pointer or __pointer__ for the mode used to represent pointers.

near

The near attribute tells the compiler that the variable is allocated in near data space (the first 8 KB of data memory). Such variables can sometimes be accessed more efficiently than variables not allocated (or not known to be allocated) in near data space.

int num __attribute__ ((near));

noload

The noload attribute indicates that space should be allocated for the variable, but that initial values should not be loaded. This attribute could be useful if an application is designed to load a variable into memory at run time, such as from a serial EEPROM.

int table1[50] __attribute__ ((noload)) = { 0 };

packed

The packed attribute specifies that a structure member should have the smallest possible alignment unless you specify a larger value with the aligned attribute.

Here is a structure in which the member x is packed, so that it immediately follows a, with no padding for alignment:

struct foo
{
char a;
int x[2] __attribute__ ((packed));
};
Note: The device architecture requires that words be aligned on even byte boundaries, so care must be taken when using the packed attribute to avoid run-time addressing errors.

page

This attribute specifies that the object cannot exceed a page boundary. The page boundary applied depends upon where the object is allocated. An object located in a psv space cannot cross a 32K boundary; an object located in prog space cannot cross a 64K boundary.

unsigned int var[10] __attribute__ ((space(auto_psv)));

The space(auto_psv) or space(psv) attribute will use a single memory page by default.

__eds__ unsigned int var[10] __attribute__ ((eds, page));

When dealing with eds, please refer to Extended Data Space Access for more information.

persistent

The persistent attribute specifies that the variable should not be initialized or cleared at startup. A variable with the persistent attribute could be used to store state information that will remain valid after a device Reset.

int last_mode __attribute__ ((persistent));

Persistent data is not normally initialized by the C run-time. However, from a cold-restart, persistent data may not have any meaningful value. This code example shows how to safely initialize such data:

#include <p24Fxxxx.h>

int last_mode __attribute__((persistent));

int main()
{
    if ((RCONbits.POR == 0) &&
        (RCONbits.BOR == 0)) {
      /* last_mode is valid */
   } else {
     /* initialize persistent data */
     last_mode = 0;
   }
}

This attribute can only be used in conjunction with a RAM resident object, i.e. not in FLASH.

preserved

The preserved attribute can be applied to a variable to indicate that this variable's value should be preserved on a restart. A restart is a user-defined event which can be different from a cold or warm reset. Preserved variables require information from a previously linked executable in order to function; please see the linker option --preserved=.

priority(n)

The priority attribute can be applied to a variable to group initializations together. n must be between 1 and 65535, with 1 being the highest level. All initializations with the same priority are initialized before moving onto the next priority level. Level 1 variables are initialized first and variables without a priority level are initialized last. The attribute can also be applied to void functions (void result and argument types); in this case the function(s) for level n will be executed immediately after all the initializations for level n are complete.

reverse (alignment)

The reverse attribute specifies a minimum alignment for the ending address of a variable, plus one. The alignment is specified in bytes and must be a power of two. Reverse-aligned variables can be used for decrementing modulo buffers in dsPIC DSC assembly language. This attribute could be useful if an application defines variables in C that will be accessed from assembly language.

int buf1[128] __attribute__ ((reverse(256)));

The reverse attribute conflicts with the aligned and section attributes. An attempt to name a section for a reverse-aligned variable will be ignored with a warning. It is an error condition to specify both reverse and aligned for the same variable. A variable with the reverse attribute cannot be placed into the auto_psv space (see the space() attribute or the -mconst-in-code option); attempts to do so will cause a warning and the compiler will place the variable into the PSV space.

section ("section-name")

By default, the compiler places the objects it generates in sections such as .data and .bss. The section attribute allows you to override this behavior by specifying that a variable (or function) lives in a particular section.

struct a { int i[32]; };

struct a buf __attribute__((section("userdata"))) = {{0}};

secure

This attribute can be used to define protected variables in Secure Segment (SS) RAM:

int __attribute__((secure)) secure_dat[16];

Variables defined in SS RAM will not be initialized on startup. Therefore all variables in SS RAM must be initialized using inline code. A diagnostic will be reported if initial values are specified on a secure variable.

String literals can be assigned to secure variables using inline code, but they require extra processing by the compiler. For example:

char *msg __attribute__((secure)) = "Hello!\n"; /* not supported */
char *msg2 __attribute__((secure));
void __attribute__((secure)) foo2()
{
 *msg2 = "Goodbye..\n"; /* value assigned explicitly */
}

In this case, storage must be allocated for the string literal in a memory space which is accessible to the enclosing secure function. The compiler will allocate the string in a psv constant section designated for the secure segment.

sfr (address)

The sfr attribute tells the compiler that the variable is an SFR and may also specify the run-time address of the variable, using the address parameter.

extern volatile int __attribute__ ((sfr(0x200)))u1mod;

The use of the extern specifier is required in order to not produce an error.

Note: By convention, the sfr attribute is used only in processor header files. To define a general user variable at a specific address use the address attribute in conjunction with near or far to specify the correct addressing mode.

shared

Used with co-resident applications. The variable may be used outside of the application. A data item will be initialized at startup of any application in the co-resident set.

space (space)

Normally, the compiler allocates variables in general data space. The space attribute can be used to direct the compiler to allocate a variable in specific memory spaces. Memory spaces are discussed further in Address Spaces The following arguments to the space attribute are accepted:
  • data – Allocate the variable in general data space. Variables in general data space can be accessed using ordinary C statements. This is the default allocation.
  • dataflash – Allocate the variable in dataflash.
  • xmemory - dsPIC30F, dsPIC33EP/F DSCs only – Allocate the variable in X data space. Variables in X data space can be accessed using ordinary C statements. An example of xmemory space allocation is:
    int x[32] __attribute__ ((space(xmemory)));
  • ymemory - dsPIC30F, dsPIC33EP/F DSCs only – Allocate the variable in Y data space. Variables in Y data space can be accessed using ordinary C statements. An example of ymemory space allocation is:
    int y[32] __attribute__ ((space(ymemory)));
  • prog – Allocate the variable in program space, in a section designated for executable code. Variables in program space can not be accessed using ordinary C statements. They must be explicitly accessed by the programmer, usually using table-access inline assembly instructions, the program space visibility window, or by the methods described in Access of Objects in Program Memory
  • auto_psv

    Allocate the variable in program space, in a compiler-managed section designated for automatic program space visibility window access. Variables in auto_psv space can be read (but not written) using ordinary C statements, and are subject to a maximum of 32K total space allocated. When specifying space(auto_psv), it is not possible to assign a section name using the section attribute; any section name will be ignored with a warning. A variable in the auto_psv space cannot be placed at a specific address or given a reverse alignment.

    Note: Variables placed in the auto_psv section are not loaded into data memory at startup. This attribute may be useful for reducing RAM usage.
  • dma - PIC24E/H MCUs, dsPIC33E/F DSCs only – Allocate the variable in DMA memory. Variables in DMA memory can be accessed using ordinary C statements and by the DMA peripheral. __builtin_dmaoffset() and __builtin_dmapage() can be used to find the correct offset for configuring the DMA peripheral. See Built-in Functions for details.
     #include <p24Hxxxx.h>
     unsigned int BufferA[8] __attribute__((space(dma)));
     unsigned int BufferB[8] __attribute__((space(dma)));
    
     int main()
     {
       DMA1STA = __builtin_dmaoffset(BufferA);
       DMA1STB = __builtin_dmaoffset(BufferB);
       /* ... */ 
     }
  • psv – Allocate the variable in program space, in a section designated for program space visibility window access. The linker will locate the section so that the entire variable can be accessed using a single setting of the PSVPAG register. Variables in PSV space are not managed by the compiler and can not be accessed using ordinary C statements. They must be explicitly accessed by the programmer, usually using table-access inline assembly instructions, or using the program space visibility window.
  • eedata - PIC24F, dsPIC30F/33F DSCs only – Allocate the variable in EEPROM Data (EEData) space. Variables in EEData space can not be accessed using ordinary C statements. They must be explicitly accessed by the programmer, usually using table-access inline assembly instructions, or using the program space visibility window.
  • pmp– Allocate the variable in off chip memory associated with the PMP peripheral. For complete details please see the Parallel Master Port Access section.
  • external – Allocate the variable in a user defined memory space. For complete details please see theExternal Memory Access section.

transparent_union

This attribute, attached to a function parameter which is a union, means that the corresponding argument may have the type of any union member, but the argument is passed as if its type were that of the first union member. The argument is passed to the function using the calling conventions of the first member of the transparent union, not the calling conventions of the union itself. All members of the union must have the same machine representation; this is necessary for this argument passing to work properly.

unordered

The unordered attribute indicates that the placement of this variable may move relative to other variables within the current C source file.

const int __attribute__ ((unordered)) i;

unsupported(message)

This attribute will display a custom message when the object is used.

int foo __attribute__((unsupported("This object is unsupported"));

Access to foo will generate a warning message.

unused

This attribute, attached to a variable, means that the variable is meant to be possibly unused. The compiler will not produce an unused variable warning for this variable.

update

The update attribute can be applied to a variable to indicate that this variable should be initialized on a restart. This is particularly useful if -mpreserve-all or --preserve-all is being used.

weak

The weak attribute causes the declaration to be emitted as a weak symbol. A weak symbol may be superseded by a global definition. When weak is applied to a reference to an external symbol, the symbol is not required for linking. For example:

extern int __attribute__((__weak__)) s;
int foo() {
 if (&s) return s;
 return 0; /* possibly some other value */
}

In the above program, if s is not defined by some other module, the program will still link but s will not be given an address. The conditional verifies that s has been defined (and returns its value if it has). Otherwise ‘0’ is returned. There are many uses for this feature, mostly to provide generic code that can link with an optional library.

The weak attribute may be applied to functions as well as variables:

extern int __attribute__((__weak__)) compress_data(void *buf);
int process(void *buf) {
 if (compress_data) {
   if (compress_data(buf) == -1) /* error */
 }
 /* process buf */
}

In the above code, the function compress_data will be used only if it is linked in from some other module. Deciding whether or not to use the feature becomes a link-time decision, not a compile time decision.

The affect of the weak attribute on a definition is more complicated and requires multiple files to describe:

 /* weak1.c */
 int __attribute__((__weak__)) i;

 void foo() {
 i = 1;
 }

 /* weak2.c */
 int i;
 extern void foo(void);

 void bar() {
   i = 2;
 }

 main() {
   foo();
   bar();
 }

Here the definition in weak2.c of i causes the symbol to become a strong definition. No link error is emitted and both i’s refer to the same storage location. Storage is allocated for weak1.c’s version of i, but this space is not accessible.

There is no check to ensure that both versions of i have the same type; changing i in weak2.c to be of type float will still allow a link, but the behavior of function foo will be unexpected. foo will write a value into the least significant portion of our 32-bit float value. Conversely, changing the type of the weak definition of i in weak1.c to type float may cause disastrous results. We will be writing a 32-bit floating point value into a 16-bit integer allocation, overwriting any variable stored immediately after our i.

In the cases where only weak definitions exist, the linker will choose the storage of the first such definition. The remaining definitions become inaccessible.

The behavior is identical, regardless of the type of the symbol; functions and variables behave in the same manner.