Global Constructors and Destructors with C (not C++)

In the OOP world, global objects get initialized with a constructor and destroyed at the end with a destructor. Interestingly, the GNU gcc has attributes to mark functions as constructor and destructors, which can greatly simply system startup and shutdown, even if not using C++.

C Function marked as Constructor and called before main()

With the GNU gcc compiler, I can mark functions with an attribute, so they get called before entering main() or after exit of main(). The attribute works both in C and C++, but it especially useful in C to initialize modules in an automated way.

Global Constructors and Destructors

In C++, when having global objects or variables, then they need to be constructed automatically at program startup, and destructed at program shutdown.

Consider the following example:

class Car {
  private: int price;
  public:
      Car(void) { // constructor
        price = 1000; // initial base price for every car
      }
      ~Car(void) { // destructor
        price = -1;
      }
      void SetPrice(int price) { // setter method
        this->price = price;
      }
};

// global variables
static Car c; // global object, shall be initialized by startup (constructor)
static int i; // initialized by startup (zero-out)
static int j = 0x1234; // initialized by startup (copy-down)

Here the program startup will call the constructor of Car c and initialize it that way, in a similar way as the startup zero-out and startup copy-down initializes variables in C. The difference is that zero-out and copy-down simply initializes the value/memory, where the constructor and destructor are function calls. All this will be handled by the startup code, and the initialization happens before calling main().

The idea is to use that concept of calling function calls before main() for normal C modules, to have them initialized and de-initialized, so I don’t have to do this manually in my code.

GCC Constructor and Destructor Attributes

The solution is to take advantage of special GNU gcc compiler attributes.

From the gcc user manual:

constructor
destructor
The constructor attribute causes the function to be called automatically before execution enters main (). Similarly, the destructor attribute causes the function to be called automatically after main () has completed or exit () has been called. Functions with these attributes are useful for initializing data that will be used implicitly during the execution of the program.

https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html

With this, I can mark my initialization and de-initialization routines with the attributes, for example:

__attribute((destructor))
void STEPPER_Deinit(void) {
  ...
}

__attribute((constructor))
void STEPPER_Init(void) {
  ...
}

I usually have Deinit() functions in my driver, in case I need to shut-down a peripheral and initialize it. As embedded systems usually ‘never end’, the Deinit() and as such the destructor might not be needed and used.

It is possible to mark a function both as constructor and destructor. It can be mixed with the prototype/declaration and definition, and vice versa:

__attribute((constructor)) void CalledAsConstructerAndDestructor(void)
__attribute((destructor)) void CalledAsConstructerAndDestructor(void) {
  /* called both in constructing and destructing phase */
}

Order of Calls

In many cases, you might want to have control over the order of calls. It is possible to give the destructor and constructor a ‘priority’, with lower numbers the higher urgency, for example:

__attribute__ ((constructor(101)))
void myInit(void) {
  ...
}

Values from 0 to 100 are reserved and used for example by the gcov. So any number from 101- 65535 can be used. If no value/argument is provided, then they get a lower priority than the ones with the number.

But VS Code Intellisense will flag the arguments as an error which can be ignored (see bug report).

attribute "constructor" does not take arguments C/C++(1094)
False Alarm in VS Code

To suppress the error, the following can be used:

#if __INTELLISENSE__
  #pragma diag_suppress 1094
#endif

Linker File

To have the init and de-init called by the startup code, I have to make sure the objects are linked and listed in a special section. Below is the added section placement for constructors and destructors:

.text : ALIGN(4)
    {
        FILL(0xff)
        __vectors_start__ = ABSOLUTE(.) ;
        KEEP(*(.isr_vector))
        /* Global Section Table */
        . = ALIGN(4) ;
        __section_table_start = .;
        __data_section_table = .;
        LONG(LOADADDR(.data));
        LONG(    ADDR(.data));
        LONG(  SIZEOF(.data));
        LONG(LOADADDR(.data_RAM2));
        LONG(    ADDR(.data_RAM2));
        LONG(  SIZEOF(.data_RAM2));
        __data_section_table_end = .;
        __bss_section_table = .;
        LONG(    ADDR(.bss));
        LONG(  SIZEOF(.bss));
        LONG(    ADDR(.bss_RAM2));
        LONG(  SIZEOF(.bss_RAM2));
        __bss_section_table_end = .;
        __section_table_end = . ;
        /* End of Global Section Table */

        *(.after_vectors*)

        /* Kinetis Flash Configuration data */
        . = 0x400 ;
        PROVIDE(__FLASH_CONFIG_START__ = .) ;
        KEEP(*(.FlashConfig))
        PROVIDE(__FLASH_CONFIG_END__ = .) ;
        ASSERT(!(__FLASH_CONFIG_START__ == __FLASH_CONFIG_END__), "Linker Flash Config Support Enabled, but no .FlashConfig section provided within application");
        /* End of Kinetis Flash Configuration data */
        
       *(.text*)
       *(.rodata .rodata.* .constdata .constdata.*)
       . = ALIGN(4);
/*-------------------------------------------------------------------------- */
        /* Constructors and Destructors */
        . = ALIGN(4);
        KEEP(*(.init))
        
        . = ALIGN(4);
        __preinit_array_start = .;
        KEEP (*(.preinit_array))
        __preinit_array_end = .;
        
        . = ALIGN(4);
        __init_array_start = .;
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        __init_array_end = .;
        
        KEEP(*(.fini));
        
        . = ALIGN(4);
        KEEP (*crtbegin.o(.ctors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*crtend.o(.ctors))
        
        . = ALIGN(4);
        KEEP (*crtbegin.o(.dtors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*crtend.o(.dtors))
        . = ALIGN(4);
        /* End Constructors and Destructors */   /*-------------------------------------------------------------------------- */
      /*-------------------------------------------------------------------------- */
    } > PROGRAM_FLASH

The same linker placement is used for C++ constructors and destructors too.

Startup Code

In the Startup code I need to make sure that the constructors and destructors get called. In a C++ system, this should be already implemented. In a C application, I have to make sure two functions in the C library get called.

First I have to add two prototypes:

extern void __libc_init_array(void); /* call constructors */
extern void __libc_fini_array(void); /* call destructors */

Then I need to make sure they get called before and after main():

Constructor and Destructor Calls around main()

Library Runtime

If you get an error during linking with

undefined reference to `_init'

then you need to check your linker file, because you have to add the crt*.o runtime support files which perform the initialization and de-initialization. Below is what I have in my linker files:

GROUP (
  "libc.a"
  "libgcc.a"
  "libm.a"
  "libc_nano.a"
  /* startup with constructor/destructor support: */
  "crti.o"
  "crtn.o"
  "crtbegin.o"
  "crtend.o"
)

Summary

With this, the constructors get called before main, and the destructors get called if I would leave main:

Constructor Function called before main()

In summary, I have now an elegant way to call initialization and de-initialization in my application.

Happy constructing and destructing 🙂

Links

6 thoughts on “Global Constructors and Destructors with C (not C++)

  1. Interesting! I’ll have to have a play with this.

    Heads up: you’ve got the prototype comments backwards under the “Startup Code” section.

    Like

  2. While I understand the allure of using these, as I have been using them for years. They can made C code be modularized, in theory, so that there are no (de)init code needed in main(). Alas I have found that they make debugging hard because of their fixed priority nature, and in future code I’m going to avoid using them. Leave that up to a proper task manager or RTOS. The could still be useful to get control of I/O at the earliest opportunity, to get it in a safe passive state until the system is ready to run.

    hardware_init_hook() and software_init_hook() that are called by newlib _start() after bss is zeroed may be a better place for such startup code.

    Also be careful that you do not have double calls to __libc_X_array().
    Some start up code such as newlib _start() already calls __libc_init_array().
    Which other than wasting time may not be noticed.

    Double calls to __libc_fini_array() are very likely to cause a bus fault.
    Consider a UART that turns itself off, then shutdown’s its clock. On the second call the UART register access will result in a bus fault because there is no clock. This is where debugging can get hard.

    Liked by 1 person

    • Hi Bob,
      thanks for your thoughts and insights! Yes, using this approach has definitely some pros and cons. I think the feature should not be over-used, and used for well defined things, as you mention.
      Double initialization is bad in most cases, but I usually avoid to add an ‘isInitialized’ flag to modules if memory usage is a concern.
      As for the startup calling fini and ini: most startups especially around C SDKs even don’t bother and don’t call it, I have even seen C++ startup code which misses to initialize the global constructors. For this it is always good to know how they work, so in case this can be fixed.
      Thanks again!

      Like

Leave a reply to Stephen Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.