news 2026/7/7 1:14:31

【effective条款4】 确定对象再使用前已经被初始化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【effective条款4】 确定对象再使用前已经被初始化

对内置类型对象进行手动初始化,因为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-elseswitch-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;};

一个对象的初始化流程,在底层经历了以下三个严格的步骤:

  1. 基类优先:如果这个类有父类,先按照继承列表的顺序初始化父类。
  2. 成员依序:接着,按照成员变量在类中从上到下声明的顺序依次初始化。
  3. 构造函数体:最后,才执行构造函数大括号{}内部的赋值代码。
    不推荐使用赋值的原因:在进入构造函数的函数体之前,会对成员变量执行默认初使化,然后再进入函数体进行赋值。这对于内置类型没有什么性能区别,但是对于复杂的类类型来说,会先调用一次默认构造函数,然后调用一次拷贝构造函数。对比使用初始化列表的方式进行初始化多了一次构造函数调用。

Non-local static对象的初使化顺序

Non-local static是指定义在非局部作用域(Non-local scope)且具有静态存储期(Static storage duration)的对象

  1. local static对象会在该函数被调用时首次遇上该对象的定义时被初使化。
  2. 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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 1:10:55

基于51单片机智能电饭煲 电饭锅 定时系统 嵌入式 支持单片机定做23(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_

基于51单片机智能电饭煲 电饭锅 定时系统 嵌入式 支持单片机定做23(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_ 功能说明&#xff1a; lcd1602液晶显示当前模式&#xff1a;煮饭、保温、煮粥 温度 煮饭时间、定时时间DS18B20温度传感器采集…

作者头像 李华
网站建设 2026/7/7 1:10:25

基于51/STM32单片机汽车防盗报警 智能家居 GSM短信 震动报警器31(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_

基于51/STM32单片机汽车防盗报警 智能家居 GSM短信 震动报警器31(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_ 功能说明&#xff1a; STM32F103C8T6单片机进行数据处理LCD1602液晶显示当前信息和参数 第一行表示当前温度和温度上限 第二行表…

作者头像 李华
网站建设 2026/7/7 1:09:02

FBI查封NetNut域名,谷歌中断200万台设备代理网络

7月2日&#xff0c;美国联邦调查局&#xff08;FBI&#xff09;和国税局刑事调查部门查封了与NetNut相关的域名&#xff0c;将该公司主页替换为联邦查封通知&#xff0c;导致这家全球最大的住宅代理服务商之一瘫痪。此次行动得到了谷歌、Lumen旗下的Black Lotus Labs以及Shadow…

作者头像 李华
网站建设 2026/7/7 1:08:03

RTCPeerConnection 连接建立:SDP 与 ICE

RTCPeerConnection 连接建立&#xff1a;SDP 与 ICERTCPeerConnection 是 WebRTC 的核心&#xff0c;负责建立点对点连接、传输音视频。这篇讲连接建立的完整流程&#xff1a;SDP 协商、ICE 候选、Offer/Answer 交换&#xff0c;一步步把连接建起来。大家好&#xff0c;我是黒漂…

作者头像 李华
网站建设 2026/7/7 1:01:44

2026年茂名老山檀香厂家推荐:这3家品质靠谱值得信赖

老山檀香市场热度持续走高&#xff0c;但产区混淆、人工优化、新料充老料等行业乱象也让不少香友踩过坑。作为一位从业多年的老山檀香源头商家&#xff0c;我走访了茂名当地多家檀香加工厂和批发点&#xff0c;结合真实手串检测数据和用户反馈&#xff0c;筛选出3家品质过硬、口…

作者头像 李华