Linker and Libraries Guide

Initialization and Termination Sections

Dynamic objects may supply code that provides for runtime initialization and termination processing. This code can be encapsulated in one of two ways, either an array of function pointers or a single code block. Each of these section types is built from a concatenation of like sections from the input relocatable objects.

The sections .preinit_array, .init_array and .fini_array provide arrays of, respectively, runtime pre-initialization, initialization, and termination functions. When creating a dynamic object, the link-editor identifies these arrays with the .dynamic tags DT_PREINIT_ARRAY and DT_PREINIT_ARRAYSZ, DT_INIT_ARRAY and DT_INIT_ARRAYSZ, and DT_FINI_ARRAY and DT_FINI_ARRAYSZ accordingly, so that they may be called by the runtime linker. The pre-initialization array is applicable to dynamic executables only. See “Initialization and Termination Routines”.

The sections .init and .fini provide, respectively, a runtime initialization and termination code block. However, the compiler drivers typically supply .init and .fini sections as part of the files they add to the beginning and end of your input file list. These files have the effect of encapsulating the .init and .fini code into individual functions that are identified by the reserved symbol names _init and _fini respectively. When creating a dynamic object, the link-editor identifies these symbols with the .dynamic tags DT_INIT and DT_FINI accordingly, so that they may be called by the runtime linker. See “Initialization and Termination Routines”.

The registration of initialization and termination functions can be carried out directly by the link-editor using the -z initarray and -z finiarray options. For example, the following command results in the address of the function foo being placed in an .initarray element, and the address of the function bar being placed in a .finiarray element.


$ cat main.c
#include    <stdio.h>

void foo()
{
        (void) printf("initializing: foo()\n");
}

void bar()
{
        (void) printf("finalizing: bar()\n");
}

main()
{
        (void) printf("main()\n");
        return (0);
}

$ cc -o main -zinitarray=foo -zfiniarray=bar main.c
$ main
initializing: foo()
main()
finalizing: bar()

The creation of initialization and termination sections can be carried out directly using an assembler, or some compilers can offer special primitives to simplify their declaration. For example, the same example containing the following #pragma definitions can result in a call to the function foo being placed in an .init section, and a call to the function bar being placed in a .fini section.


$ cat main.c
#include    <stdio.h>

#pragma init (foo)
#pragma fini (bar)

.......
$ cc -o main main.c
$ main
initializing: foo()
main()
finalizing: bar()

Be careful when designing initialization and termination code that can be included in both a shared object and archive library. If this code is spread throughout several relocatable objects within an archive library, then the link-edit of an application using this archive might extract only a portion of the objects. Therefore, the link-edit might extract only a portion of the initialization and termination code. At runtime, only this portion of code is executed. The same application built against the shared object will have all the accumulated initialization and termination code executed at runtime when the shared object is loaded as one of the application's dependencies.

Determining the sequence of executing initialization and termination code within a process at runtime is a complex issue involving dependency analysis. Initialization and termination code that references external global symbols make this process more difficult and can result in cyclic dependencies. The most flexible initialization and termination code references elements only within the resident object.

Data initialization should be independent if the initialization code is involved with a dynamic object whose memory can be dumped using dldump(3DL).