<!-- Start of markdown source --> # Introduction This article shows you how to guarantee that certain global variables in your C/C++ program are in memory in a specific order and at a specific address. Three different solutions are shown. Use the one that works best for your particular circumstances. # Background It is a common misconception that global variables written in a manner similar to ... ```c int one; int two; int three; ``` ... are guaranteed to appear in memory in that order. This is not the case. There is nothing in the standards for the C or C++ programming languages which require any order. Many compilers do emit global variables in the same order as they appear in the source. But there is no guarantee of any ordering. ### Note on Code Examples The code examples have only been tested with compilers from TI. Further, they are known to **not** work with the C28x compiler when building in COFF mode. # Solution One: Apply the Location Attribute to Each Variable Here is a complete example. ```c #define BASE 0x1000 typedef int global_type; global_type one __attribute__((location(BASE+sizeof(global_type)*0))); global_type two __attribute__((location(BASE+sizeof(global_type)*1))); global_type three __attribute__((location(BASE+sizeof(global_type)*2))); ``` This code causes those three global variables to be in memory in the same order as the source, starting at the address given by the line ... ```c #define BASE 0x1000 ``` The line ... ```c typedef int global_type; ``` defines the type of all the globals. This code works only if all the variables are the same type. The code `__attribute__((` is the first part of adding an attribute to the varible. Attributes are an extension to the C/C++ language borrowed from GCC. They are supported by all TI compilers. Attributes are always written with double parenthesis `__attribute__((/* attribute(s) here */))`. This technique makes it easy to remove all attributes at build time, by adding code similar to ... ```c #ifdef NO_ATTRIBUTES #define __attribute__(x) /* nothing */ #endif ``` By adding a compiler option such as `-DNO_ATTRIBUTES`, the preprocessor name `NO_ATTRIBUTES` is defined, which means all the attributes are removed during preprocessing. In this case the `location` attribute is used. It is supported by all TI compilers, except the C28x compiler when building in COFF mode. Not all attributes require arguments, but the location attribute does. The argument to the `location` attribute is the address of the variable. It must be a build time constant. In this case, the expression `BASE+sizeof(global_type)*0` is used. For the first variable, this expression resolves to BASE. For the remaining variables the last part of the expression changes to `+sizeof(global_type)*1` or `+sizeof(global_type)*2`. This adds the number of bytes for each preceding variable to BASE, to form the address of the current variable. ## Advantages This is the simplest of the three methods. ## Disadvantages An attribute must be added to every variable. As shown, this method requires all the variables to be the same type. If this requirement is removed, then the addresses of the `location` attribute become more difficult to express and maintain. # Solution Two: Collect the Variables in a Structure The C/C++ programming language guarantees that members of a structure are in memory in the same order as the source. This method takes advantage of that guarantee. A complete example follows. It is split into a header file and a source file. The header file is shown first. ```c /* ordered_variables.h */ #include <stdint.h> typedef float float32_t; typedef double float64_t; #define BASE 0x1000 struct ordered_variables_tag { int32_t one; float32_t two; int16_t three[100]; }; extern struct ordered_variables_tag ordered_variables; ``` Now the source file. ```c /* ordered_variables.c */ #include "ordered_variables.h" struct ordered_variables_tag ordered_variables __attribute__((location(BASE))); ``` The header file `ordered_variables.h` must be included in any file which refers to the ordered variables. The line ... ```c #include <stdint.h> ``` ... includes the standard header file which defines fixed width integer types such as `int32_t`. When definining the ordered variables, it is good to be as clear as possible about the size of these variables. It also makes the variable definitions portable across compilers and machines. The lines ... ```c typedef float float32_t; typedef double float64_t; ``` ... define the types `float32_t` and `float64_t`, fixed width floating point types. Such types are not defined in `stdint.h`. As of this writing, these are the sizes of these types on all compilers supported by TI, except C28x when building in COFF mode. The line ... ```c #define BASE 0x1000 ``` ... defines a preprocessor symbol which gives the memory address where the ordered variables begin. The lines ... ```c struct ordered_variables_tag { int32_t one; float32_t two; int16_t three[100]; }; ``` ... define the structure which contains the ordered variables. These variables are located in memory in this exact order, starting at the address given by `BASE`. These variables can be of any type. Fixed width type names are used, to make the sizes of each variable clear and portable. When these variables are accessed, they must be written similar to `ordered_variables.one` and `ordered_variables.three[i]`. This line in `ordered_variables.h` ... ```c extern struct ordered_variables_tag ordered_variables; ``` ... is a declaration of the structure. And these lines from `ordered_variables.c` ... ```c struct ordered_variables_tag ordered_variables __attribute__((location(BASE))); ``` ... are the definition of the structure. If you are unfamiliar with the terms *declaration* and *definition*, please see [this FAQ](http://www.c-faq.com/decl/decldef.html) (not from TI). ## Precisely Matching Layout Suppose you need the ordered fields to precisely match a particular layout. Perhaps you need to match a standard, or a set of registers in another piece of hardware. The solution shown so far does **NOT** guarantee a layout match. Consider this contrived example ... ```c struct ordered_variables_tag { int8_t one; float64_t two; }; ``` Consider the layout of this structure in the specific case of an ARM compiler. The field `one` is located at byte offset 0. The field `two` is not located at byte offset 1, but 8. Why? Because of alignment requirements. The instructions used to read and write 64-bit doubles work best on addresses that are a multiple of 8 bytes. The bytes between the end of `one` and the start of `two` are an unused hole in memory. The structure must start on an 8 byte boundary. So, if the address given in the `location` attribute is not a multiple of 8, an error diagnostic is issued, and the build fails. To disable alignment of fields, use the attribute `packed` ... ```c struct ordered_variables_tag { int8_t one; float64_t two; } __attribute__((packed)); ``` Note this attribute is applied to the structure type in `ordered_variables.h`, and not to the structure variable in `ordered_variables.c`. Because of the use of fixed width types, and the `packed` attribute, the layout of this structure is exactly as specified in the source. Note this solution cannot be implemented with the C28x compiler, because it does not support the `packed` attribute. ## Advantages The ordered variables can be any type, even aggregate types like arrays and structs. ## Disadvantages The implementation and maintenance is a little more complex. # Solution Three: Define the Variables in Hand-coded Assembly The full details of how to define variables in hand-coded assembly are not shown. Instead, this solution shows you how to use the compiler to discover those details. * Write a C source file which defines a small number of variables * Compile it * Inspect the automatically generated assembly code * Note which assembler directives are used * Learn about those directives in the documentation There are three cases to consider. In order of increasing complexity, these cases are: * Uninitialized Variables * `const` Variables * Initialized Variables ## Uninitialized Variables Start with the C source file. ```c /* variables_example.c */ #include <stdint.h> int16_t one __attribute__((section("ordered_variables"))); int32_t two __attribute__((section("ordered_variables"))); float three __attribute__((section("ordered_variables"))); ``` The `section` attribute puts these variables in a section named `ordered_variables`. This section name is used in the linker command file to allocate it to a specific address. Compile it with the compiler for your system. This example uses the MSP430 compiler ... ``` % cl430 --keep_asm --symdebug:none variables_example.c ``` The option `--keep_asm` causes the compiler the keep the generated assembly code file instead of deleting it. The option `--symdebug:none` disables generation of the debug information. In most other use cases, that's a bad idea. It is helpful here because, by reducing the amount of code generated, it makes the code which remains easier to understand. Inspect the file `variables_example.asm` to see the code. Here are the lines related to the variable `one`. ``` .global one one: .usect "ordered_variables",2,2 ``` `.global` and `.usect` are not CPU instructions, but directions to the assembler. They are called directives. The directives are documented in the [Assembly Language Tools User's Guide](https://www.ti.com/tool/TI-CGT#tech-docs) for the CPU family you use. Once you know how the directives work, you are ready to write the assembly code which defines all the variables needed. The assembler arranges the variables in memory exactly as specified by the source. The assembly code supplies the definition of the ordered variables. A C code declaration of these variables, in a header file, is still required. For the variables in the example above, supply a header file similar to ... ```c /* variables_example.h */ #include <stdint.h> extern int16_t one; extern int32_t two; extern float three; ``` Include this header file in any C file which accesses these ordered variables. That is how the compiler knows how to generate the correct instructions when using these memory locations. The compiler also generates debug information for these variables, so you can work with them in Code Composer Studio. In the linker command file, allocate the ordered variables to memory with code similar to ... ``` #define BASE 0x1000 ... SECTIONS { ordered_variables > BASE ... } ``` This creates an output section named `ordered_variables`. It is made up of all the input sections named `ordered_variables`. In this case, there is only one such input section. It is allocated to the hard-coded address given by the preprocessor name `BASE`. ### Notes When Using tiarmclang The above description is relevant for any of the proprietary TI compilers, such as the one for MSP430. When working with tiarmclang, which is based on the LLVM/Clang open source projects, some of the details are different. Build the variables example C source with a command similar to ... ``` % tiarmclang -S variables_example.c ``` The `-S` option tells the compiler to stop building after generating the assembly code. Then inspect the file `variables_example.s`. The LLVM assembler is based on the GNU assembler. To learn about the directives, search the [current version of the sourceware documentation for the assembler](https://sourceware.org/binutils/docs-2.37/as/index.html) (as of this writing). If it appears that link has gone stale, then visit the [main sourceware site](https://sourceware.org) to access the latest documentation. For general background on how the directives work, visit the [Converting TI-Syntax Assembly Directives to GNU-Syntax Assembly Directives](https://software-dl.ti.com/codegen/docs/tiarmclang/compiler_tools_user_guide/migration_guide/migrating_assembly_source/converting_ti_syntax_asm_to_gnu_syntax_asm/directives.html#converting-ti-syntax-assembly-directives-to-gnu-syntax-assembly-directives) sub-chapter of the [armcl to tiarmclang Migration Guide](https://software-dl.ti.com/codegen/docs/tiarmclang/compiler_tools_user_guide/migration_guide/index.html#armcl-to-tiarmclang-migration-guide). ## `const` Variables Start with a C file that has a few differences. ```c /* variables_example.c */ #include <stdint.h> const int16_t one __attribute__((section("ordered_variables"))) = 1; const int32_t two __attribute__((section("ordered_variables"))) = 2; const float three __attribute__((section("ordered_variables"))) = 3.0; ``` The directives generated by the compiler are different for this case. Everything else is the same as the uninitialized variables case. ## Initialized Variables It is best to separate this case into two steps. 1. Define the variables 2. Initialize them Implement step 1 by using the directions for the uninitialized variables case above. Implement step 2 in C. All compilers supplied by TI support boot hook functions, which are points at which you may insert application functions into the C/C++ startup code. One such function is named `_system_post_cinit`. Thus, you could add a function to the system with code similar to ... ```c #include "variables_example.h" void _system_post_cinit() { one = 1; two = 2; three = 3.0; } ``` ## Advantages This method gives you the most control over the order of the variables. ## Disadvantages This method is more difficult to implement and maintain. <!-- End of markdown source --> <div id="footer"></div>