Please note as of Wednesday, August 15th, 2018 this wiki has been set to read only. If you are a TI Employee and require Edit ability please contact x0211426 from the company directory.

Uninitialized Static Objects Not Set to Zero in COFF

From Texas Instruments Wiki
Jump to: navigation, search

Uninitialized static storage duration objects are not initialized to zero by the compiler when using COFF. The user must take extra steps to ensure these variables are initialized to zero.

What is a static storage duration object?

Static storage duration objects are

  • any global variable, whether declared static or not, and
  • any local variable declared static

What does the C standard say?

ISO 9899:1999 (the C99 standard) says, section 6.7.8 "Initialization" paragraph 10:

"If an object that has static storage duration is not initialized explicitly, then:

  • if it has pointer type, it is initialized to a null pointer;
  • if it has arithmetic type, it is initialized to (positive or unsigned) zero;
  • if it is an aggregate, every member is initialized (recursively) according to these rules;
  • if it is a union, the first named member is initialized (recursively) according to these rules."

This means that the standard requires that each of the following objects be initialized to the zero value:

       int a;    /* = 0 */
static int b;    /* = 0 */
     float c;    /* = 0.0 */
     void *d;    /* = NULL */
       int e[2]; /* = { 0, 0 } */
struct { int x; float y; } f; /* = { 0, 0.0 } */
int func() { static int g; /* = 0 */ }

Furthermore, paragraph 19 states:

"[A]ll subobjects [of an object that has an initializer] that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration."

This means that uninitialized members of partly-initialized arrays are initialized to the zero value, even if they are not static storage duration arrays!

int h[5] = { 1 }; /* = { 1, 0, 0 ,0, 0 }; */
int func() { int i[5] = { 2 }; /* { 2, 0, 0 ,0, 0 }; */

Does the TI compiler do that?

Not in COFF ABI mode. In COFF, the TI compiler avoids generating initializers for static storage duration objects because many programs have large global arrays which should technically be initialized to all zeros, but are always explicitly initialized before use during program execution, so the initialization would be useless. This initialization takes non-trivial time and space to perform and is undesirable for very small memory footprint devices, where space is at a premium.

When TI COFF was introduced, the convenience of avoiding useless initialization outweighed the convenience of strict standard conformance, so these objects are not zero-initialized. For TI EABI, the decision went the other way because it has a more efficient way to zero-initialize objects, and because portability is more important these days.

Isn't that a violation of the C standard?

This is not technically a violation of the C standard, since the linker is part of the implementation, and a method is provided for the user to get the job done, but this method is very unintuitive, and many consider it a violation. It is certainly an obstacle to ease of use. This is why the compiler does it automatically for EABI.

Will TI change this behavior?

No, there are no plans to change COFF mode to automatically zero-initialize static storage duration objects. There are already viable workarounds for this behavior, and changing COFF would risk changing the behavior of existing, working COFF projects. TI considers EABI the future path for all targets and encourages you to use EABI if you want better C and C++ support. EABI is available for C6000, MSP430, and ARM.

Remedies

The user needs to take extra action to zero-initialize such objects. There are several methods that can be used to accomplish this:

See section "Initializing Static and Global Variables in COFF ABI Mode" in the Compiler User's Guide.

  • Switch to EABI
  • Add explicit initializers in the source code
  • Add fill value in linker command file for sections representing initializers (e.g. .bss)
  • Zero the .bss section before loading

How is auto-initialization implemented in COFF?

For each non-const uninitialized static storage duration object, the compiler generates a .cinit record. This record has fields corresponding to the structure of the object being initialized. Each record also has a field indicating the starting address of the object it represents, and the length of the data in the .cinit record. The .cinit record itself is const data. The linker concatenates all of the input .cinit records and places them in a single section in the COFF executable object file. There are two models for performing auto-initialization, ROM model and RAM model.

In ROM model, the combined .cinit section is written to target memory and processed at startup time. The boot routine calls the auto-initialization function (imaginatively named something like auto_init) which reads the individual .cinit records and copies the data to the right location.

In RAM model, the loader (e.g. CCS) reads the COFF object file, extracts the .cinit section, and performs the initialization at load time, writing the values to target memory through the target interface. In this model, the auto-initialization function does nothing at boot time.

The finer details of auto-initialization are outside the scope of this page. See the Compiler User's Guide for more details.

Partly-initialized arrays and structs

In COFF mode, the .cinit record will contain as many initializer values as the user explicitly specified, and no more. For instance, for the following source:

int a[3] = { 1, 2 };
struct { int x,y; } b = { 3 };

The C6000 compiler will generate the following .cinit records in COFF mode:

        .field  8,32  ; length of the data in bytes
        .field  _a,32 ; address of the data
        .bits   1,32  ; the data
        .bits   2,32  ; the data

        .field  4,32  ; length of the data in bytes
        .field  _b,32 ; address of the data
        .bits   3,32  ; the data

At auto-initialization time, the first part of each object will be initialized, and the rest of it will have whatever value it happened to have upon RESET.

GCC or C99 designated initializers

In GCC or C99 mode, you can initialize aggregate members by name in any order you please with Designated Initializers:

struct { int a, b, c, d; } s = { .c = 1, .b = 2 }; /* = { 0, 2, 1, 0 } */
int e[3] = { [1] = -1 }; /* = { 0, -1, 0 } */

The way this is implemented in COFF mode is to add just enough zero-initialized members to the .cinit record to push the last explicitly initialized member into the right spot. Any uninitialized members after the last explicitly initialized member are not added. For instance, for the above source code, the 6000 compiler will generate the following .cinit records in COFF mode:

        .field  12,32 ; length of the data in bytes
        .field  _s,32 ; address of the data
        .bits   0,32  ; the data
        .bits   2,32  ; the data
        .bits   1,32  ; the data

        .field  8,32  ; length of the data in bytes
        .field  _e,32 ; address of the data
        .bits   0,32  ; the data
        .bits   -1,32 ; the data

How does this affect C++ global objects?

The compiler keeps a flag for each C++ global object that indicates whether it has been constructed yet or not. This flag is a "plain old data" object, and it is a static storage duration uninitialized variable. This means you need to arrange to have these variables initialized to zero, or the object may not get constructed properly. This variable is created by the compiler; there is no way to explicitly initialize it, so you need to use one of the other methods to zero out the section it is in.

What about const objects?

Sometimes uninitialized const objects are initialized by the compiler as the standard requires, and sometimes they are not. Before we get into that, why do you want an uninitialized const object - do you really want a known zero in memory? There are cases where you might want that, but it's rare. Consider explicitly initializing the whole object instead.

In COFF mode, the compiler only uses the .cinit auto-initialization scheme for static storage duration objects that are not initialized with direct initialization. Most const static storage duration objects are initialized with direct initialization, which means the initial value will be stored in the variable itself in the executable object file. No .cinit record is generated for a direct-initialized variable, so the initializer is not truncated to the explicitly-initialized portion, which means direct-initialized uninitialized objects are zero-initialized as the standard requires.

The general rule of thumb for COFF is that const static storage duration objects that do not have a constructor are direct-initialized. However, in some memory models for some targets, not all such objects are direct-initialized, in which case it is subject to .cinit auto-initialization. See the Compiler User's Guide for a description of the memory models for your target. Some targets will auto-initialize const objects for various reasons. (For example, on C6000, all "near" objects must be placed in .bss, and all variables in .bss are subject to auto-initialization.)

What's this --zero_init option?

Don't use it. It doesn't affect COFF, and using --zero_init=off completely disables auto-initialization of uninitialized objects. Basically, it makes EABI have the COFF behavior, which is almost surely not what you want.

What's this NOINIT linker command file keyword?

What's this persistent attribute/pragma?

It's an EABI-only feature that...

What's this noinit attribute/pragma?

It's an EABI-only feature that...