C++

Gcc 使用 init priority 控制全局变量初始化顺序

Posted by w@hidva.com on June 30, 2016

init_priority

在 C++ 标准中,对于全局变量的初始化顺序,只规定了若全局变量的定义在同一 translation unit (可以理解为:源文件) 中,则定义的顺序决定了初始化的顺序,并没有规定跨 translation unint 的情况下初始化顺序.

gcc 中可以通过__attribute__((__init_priority__(PRIORITY)))在全局变量定义时指定其初始化 顺序,这里PRIORITY值越小,优先级越高;并且PRIORITY的合法范围:[101,65535].若若干全局变量 的init_priority值相同,则试验发现:此时相当于这些全局变量都未指定init_priority的规则 来整.

对于跨 so 的全局变量,其初始化顺序依赖于 so 之间的依赖关系;如:若 A.so 依赖于 B.so,则很显然 B.so 的初始化代码段先于 A.so 执行,即 B.so 中的全局变量先于 A.so 中的全局变量初始化.

所以,我们只需要考虑组成同一 so(或可执行文件) 的众多 translation unit 中全局变量的初始化顺序.

  • 情况一:这些全局变量均未使用init_priority;则同一 translation unit 中的全局变量初始化顺序按照 标准来整;跨 translation unint 的全局变量,其初始化顺序取决于链接时,全局变量定义所在’.o’ 在命令行参数中的出现顺序.

    这部分均属试验发现,因为没找到这部分的标准资料;对于跨 translation unit,若在链接时 A.o 出现在 B.o 之前,则根据 gcc 版本不同,A.o 中的全局变量初始化可能先于 B.o 中的全局变量, 也可能后于.

  • 情况二:所有全局变量均使用init_priority;则按照 GCC 标准来整.

  • 情况三:部分全局变量使用了init_priority,部分没有;则试验发现(也是因为没有找到相关文档), 所有使用了init_priority的全局变量(下称 As),其初始化顺序先于未使用init_priority 的全局变量(下称 Bs);

    对于 As 中包含的全局变量,其初始化顺序取决于优先级的值.对于 Bs 中包含的全局变量,其初始化 顺序取决于’情况一’

模版与 init_priority

这里讲述了模板与init_priority的交互;注意以下结论均是试验发现(因为没找到标准文档).

# 本次试验中所使用 g++ 版本.
$ g++ -v
使用内建 specs。
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/5.4.0/lto-wrapper.exe
目标:x86_64-pc-cygwin
配置为:/cygdrive/i/szsz/tmpp/gcc/gcc-5.4.0-1.x86_64/src/gcc-5.4.0/configure --srcdir=/cygdrive/i/szsz/tmpp/gcc/gcc-5.4.0-1.x86_64/src/gcc-5.4.0 --prefix=/usr --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --docdir=/usr/share/doc/gcc --htmldir=/usr/share/doc/gcc/html -C --build=x86_64-pc-cygwin --host=x86_64-pc-cygwin --target=x86_64-pc-cygwin --without-libiconv-prefix --without-libintl-prefix --libexecdir=/usr/lib --enable-shared --enable-shared-libgcc --enable-static --enable-version-specific-runtime-libs --enable-bootstrap --enable-__cxa_atexit --with-dwarf2 --with-tune=generic --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-graphite --enable-threads=posix --enable-libatomic --enable-libcilkrts --enable-libgomp --enable-libitm --enable-libquadmath --enable-libquadmath-support --enable-libssp --enable-libada --enable-libgcj-sublibs --disable-java-awt --disable-symvers --with-ecj-jar=/usr/share/java/ecj.jar --with-gnu-ld --with-gnu-as --with-cloog-include=/usr/include/cloog-isl --without-libiconv-prefix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --with-default-libstdcxx-abi=gcc4-compatible
线程模型:posix
gcc 版本 5.4.0 (GCC)
// 本次试验中所使用的公共代码,后续标识符均是引用这里的标识符.

#include <stdio.h>

struct X {
    X(const char *name):
        name_(name)
    {
        printf("%s;this: %p;name: %s\n",__PRETTY_FUNCTION__,this,name_);
    }
    
    ~X()
    {
        printf("%s;this: %p;name: %s\n",__PRETTY_FUNCTION__,this,name_);
    }

private:
    const char *name_ = nullptr;
};


template <typename Type>
struct Y {
    static X x;
};

template <typename Type>
__attribute__((init_priority(333))) X Y<Type>::x("hello");
  • 试验1;结论:仅当用到模板类的成员时,该成员才会被实例化.

    int
    main()
    {
        Y<int> a;
        Y<double> b;
    }
    
    $ g++ -Wall -std=gnu++11 main.cc
    $ ./a.out # 没有输出,因为此时 Y::x 并未实例化!
    $ 
    
  • 试验2;结论:模板类的实例化类似于简单的宏替换;

    int
    main()
    {
        void *ptr1 = &Y<int>::x;
        void *ptr2 = &Y<double>::x;
        return 0;
    }
        
    // 这里生成的 Y<int> 代码如下:
        
    struct Y_int {
        static X x;
    };
        
    __attribute__((init_priority(333))) X Y_int::x("hello");
    
    // 这里生成的 Y<double> 代码如下:
        
    struct Y_double {
        static X x;
    };
        
    __attribute__((init_priority(333))) X Y_double::x("hello");
        
    

    此时Y<int>::x,Y<double>::x的初始化顺序取决于编译器实例化的顺序,在本例中,编译器先遇到Y<int>, 即Y<int>::x也被实例化,因此也先被初始化.

  • 试验3;结论:如下:

        
    template <>
    __attribute__((init_priority(333))) X Y<int>::x("int");
        
    int
    main()
    {
        void *ptr2 = &Y<double>::x;
        void *ptr1 = &Y<int>::x;
        return 0;
    }    
        
    

    注意,此时Y<int>::x先于Y<double>::x初始化;因为Y<int>::x已经是一个完整的变量定义了,它不再是模板了.