对内置类型对象进行手动初始化,因为c++不保证初始化它们。
对于内置类型,有些场景会正常初使化,但是有些场景不会(比如局部变量)。如果不进行初使化,变量是个随机值,这在后面的使用可能会导致未知错误。
c++不初始化内置类型的原因:
1.为了性能。 C++ 的设计原则:“你不用为不使用的东西付出代价”。在底层,初始化一个变量(比如把一个int设为 0),意味着 CPU 必须执行一条内存写入指令。这会拖慢CPU的执行效率。
2. 兼容 C 语言。
使用工具检查未初使化变量
gcc编译阶段检查:
-O1-Wuninitialized-Wmaybe-uninitialized-O1/-O2/-O3(前置条件)- 如果你的项目处于
-O0(不优化)模式,GCC 的未初始化检查是直接失效的! - 底层原因:未初始化检查属于高级数据流分析。只有开启了优化(哪怕是最基础的
-O1),GCC 才会去构建函数的控制流图、分析变量的生命周期。如果在调试阶段(Debug)想抓这个 Bug,切记至少要开到-O1。
- 如果你的项目处于
-Wuninitialized- 作用:开启基础的未初始化检查。当 GCC 能够100% 确定某条代码路径上一个局部变量在没有被赋值的情况下就被读取了,就会弹出警告。
-Wmaybe-uninitialized- 作用:开启“可能未初始化”的检查。它对付的是那些复杂的逻辑分支(比如
if-else或switch-case)。如果变量只在某些分支里赋了值,但在另一些分支里没赋值、且后续被读取了,GCC 就会报这个警告。
示例:
- 作用:开启“可能未初始化”的检查。它对付的是那些复杂的逻辑分支(比如
```cpp #include <iostream> int main(int argc, char* argv[]) { int a; std::cout<<a<<std::endl; }对于上面的代码在执行带检查参数的时候g++ --std=c++11 -O1 -Wuninitialized -Wmaybe-uninitialized noinitialized.cpp会有下面的告警:
noinitialized.cpp:4:16: 警告:‘a’未经初始化被使用 [-Wuninitialized] 4 | std::cout<<a<<std::endl; | ^ noinitialized.cpp:3:9: 附注:‘a’在此声明 3 | int a; | ^使用Clang-Tidy进行静态检查
clang-tidy noinitialized.cpp -header-filter=.*-checks='cppcoreguidelines-pro-type-member-init,clang-analyzer-core.UndefinedBinaryOperatorResult'---std=c++11会有如下报错:
5 warnings generated. noinitialized.cpp:4:5: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage] 4 | std::cout<<a<<std::endl; | ^ ~ noinitialized.cpp:3:5: note: 'a' declared without an initial value 3 | int a; | ^~~~~ noinitialized.cpp:4:5: note: 1st function call argument is an uninitialized value 4 | std::cout<<a<<std::endl; | ^ ~构造函数初使成员变量
构造函数初使化成员变量尽量优先使用初始化列表的方式进行初使。原因分析如下:
区分赋值和初始化:
// 初始化示例classStorageNode{public:// 正确示范:使用初始化列表StorageNode(intid,conststd::string&path):node_id(id),meta_path(path)// 顺便提一句:这里的顺序最好跟下面声明的一致{// 此时成员变量已经完全初始化好了,这里通常是空的}private:intnode_id;std::string meta_path;};// 赋值示例classStorageNode{public:StorageNode(intid,conststd::string&path){node_id=id;// 这是赋值,不是初始化!meta_path=path;// 这是赋值,不是初始化!}private:intnode_id;std::string meta_path;};一个对象的初始化流程,在底层经历了以下三个严格的步骤:
- 基类优先:如果这个类有父类,先按照继承列表的顺序初始化父类。
- 成员依序:接着,按照成员变量在类中从上到下声明的顺序依次初始化。
- 构造函数体:最后,才执行构造函数大括号
{}内部的赋值代码。
不推荐使用赋值的原因:在进入构造函数的函数体之前,会对成员变量执行默认初使化,然后再进入函数体进行赋值。这对于内置类型没有什么性能区别,但是对于复杂的类类型来说,会先调用一次默认构造函数,然后调用一次拷贝构造函数。对比使用初始化列表的方式进行初始化多了一次构造函数调用。
Non-local static对象的初使化顺序
Non-local static是指定义在非局部作用域(Non-local scope)且具有静态存储期(Static storage duration)的对象
- local static对象会在该函数被调用时首次遇上该对象的定义时被初使化。
- non-local static对象的初始化顺序
- 在同一个
.cpp源文件里,non-local static对象严格按照它们从上到下的出现顺序依次初始化。 - C++ 标准不保证不同源文件(
.cpp)之间静态对象的初始化顺序!
这也就是说对于不同源文件中的non-local static对象之间如果有依赖关系,那么就很有问题了。那么怎么解决这个问题呢——用local static对象替换non-local static对象,即常见的单例模式。
// 将全局变量换成一个返回引用的静态函数inlinestd::string&GetGlobalConfigPath(){// 完美的保底:谁先调用这个函数,这个静态字符串就会瞬间当场初始化staticstd::string config_path="/etc/kv_server.conf";returnconfig_path;}// 别的地方使用时:LoadConfig(GetGlobalConfigPath());// 绝对安全,100% 不可能读到未初始化的状态使用asan定位初始化顺序问题
// file1.cppintget_init_value(){return10;}inta=get_init_value();// file2.cpp#include<iostream>externinta;intb=a+1;intmain(intargc,char*argv[]){std::cout<<b<<std::endl;return0;}这个代码可以正常编译运行,但是输出的结果不是预期的11而是1。
使用带asan参数的g++命令编译:g++ -fsanitize=address -g file2.cpp file1.cpp -o asan_init_test --std=c++11,然后开启初始化检测参数执行编译好的二进制文件:ASAN_OPTIONS=check_initialization_order=true:strict_init_order=true ./asan_init_test。会输出如下报错:
==29992==ERROR: AddressSanitizer: initialization-order-fiasco on address 0x000000403380 at pc 0x0000004005a7 bp 0x7ffee40ddf00 sp 0x7ffee40ddef8 READ of size 4 at 0x000000403380 thread T0 #0 0x0000004005a6 in __static_initialization_and_destruction_0 effective_cpp/item_4/file2.cpp:4 #1 0x0000004005fc in _GLOBAL__sub_I_b effective_cpp/item_4/file2.cpp:9 #2 0x7f405ac106e3 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x36e3) (BuildId: ff0267465bc3d76e21003b3bc5598fd5ee63e261) #3 0x000000400444 in _start (effective_cpp/item_4/app+0x400444) (BuildId: 648f97369c399543264dd0fc39bf1ec9655852a9) 0x000000403380 is located 0 bytes inside of global variable 'a' defined in 'file1.cpp:5:5' (0x000000403380) of size 4 registered at: #0 0x7f405b21be68 in __asan_register_globals.part.0 (/lib64/libasan.so.8+0x1be68) (BuildId: 25975f766867e9e604dc5a71a8befeaed3301942) #1 0x0000004006d3 in _sub_I_00099_1 (effective_cpp/item_4/app+0x4006d3) (BuildId: 648f97369c399543264dd0fc39bf1ec9655852a9) #2 0x7f405ac106e3 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x36e3) (BuildId: ff0267465bc3d76e21003b3bc5598fd5ee63e261) #3 0x000000400444 in _start (effective_cpp/item_4/app+0x400444) (BuildId: 648f97369c399543264dd0fc39bf1ec9655852a9) SUMMARY: AddressSanitizer: initialization-order-fiasco effective_cpp/item_4/file2.cpp:4 in __static_initialization_and_destruction_0 Shadow bytes around the buggy address: 0x000000403100: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 0x000000403180: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x000000403200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403300: 00 00 00 00 04 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 =>0x000000403380:[f6]f6 f6 f6 f6 f6 f6 f6 00 00 00 00 00 00 00 00 0x000000403400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x000000403600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==29992==ABORTING