static变量初始化时间

本文解析了C++中静态变量的初始化过程,区分静态初始化与动态初始化,并介绍了不同类型的静态变量(全局、局部、类成员)的初始化时机及内存分配。
  • 在C++中,静态变量分为全局静态变量(属于全局变量)、局部静态变量(函数中的静态变量)和类中静态成员变量。按照初始化的类型分为静态初始化(static initialization)和动态初始化(dynamic initialization)。
  1. 静态初始化
    static initialization:指的是用常量来对静态变量进行初始化,包括zero initialization和const initialization;其中zero initialization的变量会保存在.bss段(未初始化静态变量,以及初始化为0的静态变量);const initialization的变量保存在.data段(已经初始化为非0的静态变量)。对于静态初始化的变量(请注意:包括在函数中采用静态初始化的静态变量),是在程序加载时完成的初始化。

  2. 动态初始化
    dynamic initialization:指的是需要调用函数才能完成的初始化,比如说:int a = foo(),或者是复杂类型(类)的初始化(需要调用构造函数)等,对于这种全局静态变量、类的静态成员变量,是在main()函数执行前,加载时调用相应的代码进行初始化的。而对于局部静态变量,是在函数执行至此初始化语句时才开始执行的初始化(运行时初始化)**。

全局static变量

  1. 如果采用静态初始化,是在编译时完成的初始化,即是在main()函数执行前由运行时调用相应的代码进行初始化的。
  2. 如果采用动态初始化,是由加载时调用的,会在main()函数执行前由运行时调用相应的代码进行初始化的。

局部static变量

  1. 如果采用静态初始化,是在加载时完成的初始化,会在main()函数执行前由运行时调用相应的代码进行初始化的;
  2. 如果采用动态初始化,是在运行时完成的初始化, 是函数执行至此语句完成初始化的

类static成员变量

  1. 如果采用静态初始化,是在加载时完成的初始化,即是在main()函数执行前由运行时调用相应的代码进行初始化的。
  2. 如果采用动态初始化,也是在加载时调用的,会在main()函数执行前由运行时调用相应的代码进行初始化的。

初始化时间

  1. 编译时初始化
  • 在源代码被编译过程中,编译期会加入代码逻辑,以完成确定的内存分配 和 变量的初始化。分配内存,并非实际分配内存,而是写入其内存分配大小信息。
  • 在编译期初始化,那么在实际运行期都是确定的结构和逻辑,将带来更高的性能,因为编译器完成了一定的工作。
  //如:全局变量为内置类型,并且大小确定
  int a=2; 
  static int b=3;  
  static int c=a+b;
  • 是针对于那些简单的、c++内部定义的数据结构(也称 内置结构),如int,double,bool 及 数组结构 的初始化,又可分为两种方式:
    • .bss段 未初始化的变量,也就是我们没指定初值,编译器分配0值给它,编译时编译器将其分配在.bss段,不占用rom空间。
    • .data 段 已初始化的变量,也就是我们指定了初值,编译器将其分配在.data段,占用rom空间。
      注明:bss段不占用rom空间,就是不占用内核镜像的空间,但是在内核加载到内存时,会保留相应的空间。
  1. 加载时初始化
  • 全局类对象在main函数执行前,由加载程序完成其初始化,其无法在编译期初始化,由于那时候还无法调用类的构造函数。同时,在加载期,是线程安全的。例如,饿汉方式的单例类。借助main执行前的加载期完成初始化,由于还在加载,所以确保线程安全。
  1. 运行时初始化
  • 指代实际程序运行期间对象(变量)的创建,包含那些动态创建的对象。由于编译和加载时无法确定大小,因此它们只能延迟到运行期才能完成初始化,将带来程序的性能开销。并且由于运行期间可能是多线程环境,对于共享变量,还可能带来线程安全问题。
  1. 只有采用动态初始化的局部static变量,是在函数执行到此语句时完成初始化的;其余情况的static变量,均会在main()函数执行前执行相应的代码进行初始化,区别仅在于是在编译时执行的相应代码,还是加载时执行的相应代码;
  2. static变量,若类型为复杂类型(类),则采用动态初始化;若为全局的static复杂类型,则在加载时调用,在main()函数之前执行;若为局部的static复杂类型,则在运行时函数执行至此语句完成初始化;
  3. c++规定,const的静态成员可以直接在类内进行初始化,而非const的静态成员需要在类外声明以初始化,对于后一种情况,我们一般选择在类的实现文件中初始化。至此,具体的初始化方式和上面说的是一致的,可在编译期间初始化,也可在运行期间初始化。

初始化顺序

  1. 原则上,定义在任何函数之外的变量(即,全局变量、名字空间变量以及类 static 变量)在main()被调用前初始化。同一个编译单元中的非局部变量按它们的定义顺序进行初始化。
  2. 不同编译单元中全局变量的初始化顺序无法保证一致。因此,在不同编译单元的全局变量初始值顺序间建立依赖关系是不明智的做法。此外,全局变量初始化时抛出的异常也不可能被捕获。一般来说我们最好尽量减少全局变量的使用,特别是限制使用需要复杂初始化的全局变量。
  3. 针对函数内部的局部static变量,其在第一次被调用时初始化,并且只初始化一次。

内存分配时间及释放时间

  • static 成员变量是在初始化时分配内存的,程序结束时释放内存。

C++11 magic static 特性

  • 局部static变量,如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
  • 这样的话后进入的并发线程一定会阻塞到变量完成初始化后才继续执行,保证了线程安全性。

总结

  • 静态初始化先于动态初始化。因为静态初始化发生在编译时期,直接写进.bss段和.data段,在程序执行时直接加载;而动态初始化则是在运行时期,由运行时库调用相应构造函数进行初始化,同样要写进.bss段或.data段。
  • .data段和.bss段的区别:
    • .data段存放的是已初始化好的全局变量和静态变量;
    • .bss段存放的是未初始化的全局变量和静态变量,给其赋0值【在有些编译器中,初始化为0的静态变量和全局变量也放在.bss段】
### C++ 中类的 `static` 变量初始化方法 在 C++ 中,`static` 成员变量属于整个类而非某个具体对象。因此,在类定义中仅能声明这些静态成员变量,而它们的实际初始化必须在类外完成。 #### 静态成员变量初始化规则 1. **类外单独初始化** 在类定义之外对 `static` 成员变量进行显式的初始化操作是必要的[^2]。这是因为静态成员变量的生命周期独立于任何实例化对象,其存储空间由编译器统一管理并分配。 ```cpp class MyClass { public: static double myStaticVar; }; // 类外初始化 double MyClass::myStaticVar = 3.14; ``` 2. **整型常量特殊情况 (C++98)** 如果静态成员是一个整数类型的常量,则可以在类内部直接赋初值[^3]。然而需要注意的是,这种方式适用于早期标准(如 C++98),而在现代 C++ 版本中已有所扩展支持更多数据类型。 ```cpp class AnotherClass { public: static const int MAX_SIZE = 100; // 整型常量可以直接在类内初始化 }; ``` 3. **C++11 新特性:类内初始值设定** 自从引入 C++11 后,允许通过特定语法形式为非整型静态成员提供默认值作为占位符,尽管最终仍需在外部分配实际内存位置[^4]。 ```cpp class ModernClass { public: static double piValue = 3.14159; // 使用 C++11 的新功能设置初步数值 }; // 即使有了上面的定义,仍然要执行下面这一步骤来正式确立该变量的存在域及其地址关联关系 double ModernClass::piValue; ``` 4. **指针或复杂结构体/容器类型的处理** 对于指向其他资源或者更复杂的集合类型来说,往往采取先将其置零再按需动态创建策略[^3]。 ```cpp class ComplexExample { public: static ComplexExample* pInstance; static std::map<std::string, std::string> dataStore; private: ComplexExample() {} }; // 显式指定初始状态为空 ComplexExample* ComplexExample::pInstance = nullptr; std::map<std::string, std::string> ComplexExample::dataStore; ``` 以上便是关于如何正确地针对不同种类的数据实施相应的初始化措施总结说明。 ```cpp // 综合示例展示多种情况下的实现手法 #include <iostream> #include <map> class Example { public: static const int SIMPLE_CONSTANT; // 声明只读简单整数 static double FLOATING_POINT_NUMBER; // 浮点数例子 static Example* SINGLETON_INSTANCE; // 单件模式中的全局唯一实体代表 static std::map<int, std::string> LOOKUP_TABLE;// 键值映射表样例 private: Example(){} }; const int Example::SIMPLE_CONSTANT = 42; // 定义并赋予固定不变的值 double Example::FLOATING_POINT_NUMBER = 2.718; // 提供近似自然底数e的一个估值 Example* Example::SINGLETON_INSTANCE = new Example();// 实际生成单子模型的对象句柄 std::map<int,std::string> Example::LOOKUP_TABLE={{1,"One"},{2,"Two"}};// 初始化一些预设条目 int main(){ std::cout << "Constant Value: " << Example::SIMPLE_CONSTANT << "\n"; std::cout << "Floating Point Number: " << Example::FLOATING_POINT_NUMBER << "\n"; delete Example::SINGLETON_INSTANCE; // 清理之前建立起来的那个独一无二个体 Example::SINGLETON_INSTANCE=nullptr; // 将引用重置回安全未绑定状况 auto it=Example::LOOKUP_TABLE.find(2); if(it!=Example::LOOKUP_TABLE.end()) std::cout<<"Lookup Result:"<<it->second<<"\n"; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值