Using linker segments and __declspec(allocate(…)) to arrange data in a specific order


You can declare a section and then start generating data into it.

#pragma section("mydata$a", read, write)  
__declspec(allocate("mydata$a")) int i = 0;

#pragma section("mydata$b", read, write)  
__declspec(allocate("mydata$b")) int j = 0;

The #pragma section directive lets you define a new section and assign attributes. You can then place data into that section with the __declspec(allocate(...)) attribute.

When the linker combines all the little bits and pieces of data, it does the following:

  • It takes the section names and splits them at the first dollar sign. (If there is no dollar sign in the section name, then the entire string is treated as the "before the first dollar sign" portion.)
  • The portion before the dollar sign is the name of the section in the generated module.
  • The portion after the dollar sign, if any, is used to sort the fragments within a section.

It is common to take advantage of the "sorts the data fragments alphabetically" step by generating data into a carefully-named sequence of sections so that they can iterate over all the objects in the middle section:

typedef void (*INITIALIZER)();

#pragma section("mydata$a", read)  
__declspec(allocate("mydata$a")) const INITIALIZER firstInitializer = nullptr;

#define ADD_INITIALIZER_TO_SECTION(fn, s) \
    __declspec(allocate("mydata$" s)) \
    const INITIALIZER initializer##fn = fn

#pragma section("mydata$g", read)  
#pragma section("mydata$m", read)  
#pragma section("mydata$t", read)  

#define ADD_EARLY_INITIALIZER(fn) ADD_INITIALIZER_TO_SECTION(fn, "g")
#define ADD_INITIALIZER(fn)       ADD_INITIALIZER_TO_SECTION(fn, "m")
#define ADD_LATE_INITIALIZER(fn)  ADD_INITIALIZER_TO_SECTION(fn, "t")

#pragma section("mydata$z", read)  
__declspec(allocate("mydata$z")) INITIALIZER lastInitializer = nullptr;

// In various files

// file1.cpp
ADD_INITIALIZER(Function1);

// file2.cpp
ADD_INITIALIZER(Function2);
ADD_LATE_INITIALIZER(DoThisLater2);

// file3.cpp
ADD_INITIALIZER(Function3);
ADD_EARLY_INITIALIZER(DoThisSooner3);

// file4.cpp
ADD_EARLY_INITIALIZER(DoThisSooner4);
ADD_LATE_INITIALIZER(DoThisLater4);

The idea is that anybody who needs to add an initializer declares a function pointer in the mydata$g, mydata$m, or mydata$t section. The linker will collect all of those function pointers from same-named sections together, and then sort the sections, so that the final order of fragments in the mydata section is

mydata$a firstInitializer main.obj
mydata$g DoThisSooner3 file3.obj unspecified
order
DoThisSooner4 file4.obj
mydata$m Function2 file2.obj unspecified
order
Function1
file1.obj
Function3 file3.obj
mydata$t DoThisLater2 file2.obj unspecified
order
DoThisLater4 file4.obj
mydata$z lastInitializer main.obj

The Initialize­All­The­Things function then walks through all the function pointers between first­Initializer and last­Initializer and calls each one.

The alphabetical ordering rule ensures that the mydata$a fragment comes first, so that first­Initializer has the lowest address. Next comes the mydata$g fragments, which contain the early initializers. Following that are the mydata$m fragments, which are the regular initializers. Next are the mydata$t fragments, which contain the late initializers. And finally the mydata$z fragment, which contains last­Initializer.

Now that we understand the principle behind section grouping and sorting, we can look at the gotchas next time.

Comments (5)

  1. Henke37 says:

    This sounds eerily familiar. Not the ordering, but the rest. It reminds me of c++ global objects and their initialization order.

    1. Indeed, this is how C++ global object initialization is implemented in the Microsoft compiler.

  2. I’m expecting something like:

    for (INITIALIZER *i = &firstInitializer + 1; i < &lastInitializer; i++) {
    (**i)();
    }

    That distant rumbling sound was comp.lang.c exploding.

  3. Brian_EE says:

    Source code linker placement directives like this are well known (and used a lot) in the embedded world. As Raymond himself has been known to say – “How cute.”

  4. uffa8 says:

    problem here if we write
    const INITIALIZER initializer_fn = fn;
    and no reference to address of initializer_fn – compiler not generate variable for initializer_fn
    for force generate variable we can use fake local function (unreferenced local function has been removed):

    static void fakeFn() { if (&initializer_fn) __debugbreak(); }

    where we reference address &initializer_fn

    also instead #pragma section(“mydata$x”, read) and __declspec(allocate(“mydata$x”)) const INITIALIZER x = fn; better use
    #pragma const_seg(“mydata$x”)
    const INITIALIZER x = fn;
    static void fakeFn() { if (&x) __debugbreak(); }

    so complete [code can look like](https://pastebin.com/32RehATA)

    #define echo(x) x

    #define random_var echo(__)echo(__LINE__)

    #define MAKE_VAR(x) static void echo(fakeFn)echo(__LINE__)() { if (&x)__debugbreak(); }

    #define ADD_INITIALIZER(section, function) \
    __pragma(const_seg(section)) \
    static INITIALIZER random_var = function; \
    MAKE_VAR(random_var)

    typedef void (__cdecl * const INITIALIZER)();

    #pragma const_seg("mydata$a")
    INITIALIZER firstInitializer = 0;

    #pragma const_seg("mydata$z")
    INITIALIZER lastInitializer = 0;

    void InitializeAllTheThings(INITIALIZER * pfbegin, INITIALIZER * pfend) {
    do {
    if (INITIALIZER initializer = *pfbegin++) initializer();
    } while(pfbegin < pfend);
    }

    void InitializeAllTheThings()
    {
    InitializeAllTheThings(&firstInitializer, &lastInitializer);
    }

    void __cdecl someFunc1();
    void __cdecl someFunc2();
    void __cdecl someFunc3();

    #pragma warning(disable : 4505) // unreferenced local function has been removed

    ADD_INITIALIZER("mydata$u", someFunc1)
    ADD_INITIALIZER("mydata$u", someFunc2)
    ADD_INITIALIZER("mydata$c", someFunc3)

    #pragma warning(default : 4505) // unreferenced local function has been removed

Skip to main content