C++的未定义行为:从编译器优化到安全编程

简介: 未定义行为(Undefined Behavior,UB)是C++语言中一个极具争议的特性

未定义行为(Undefined Behavior,UB)是C++语言中一个极具争议的特性。它指的是语言标准没有规定程序行为的情况,编译器可以任意处理——可能产生预期结果,也可能崩溃,或者更隐蔽地导致安全漏洞。理解UB的来源、后果以及避免方法,是写出可靠C++代码的前提。

C++标准中列出了数百种未定义行为,常见的有:有符号整数溢出、访问越界指针、解引用空指针、使用未初始化的变量、多次修改同一变量而无序列点(C++11前)、违反严格别名规则、函数没有返回值、delete后使用指针、等等。标准之所以将这些行为定义为UB,而非要求运行时检查,是为了给编译器最大的优化空间。例如,假设没有溢出,编译器可以优化有符号整数运算;假设指针不为空,可以消除冗余的空检查。

编译器利用UB进行激进优化,有时会导致程序行为“时空倒流”。经典案例:有符号整数溢出优化导致无限循环被删除;空指针解引用检查被编译器视为不可能路径,从而移除后续的安全检查;甚至出现“时间旅行”现象——未定义行为可以向前影响到之前已执行的代码。这些看似违反直觉的行为,实际上是编译器根据标准所做的合法变换。

UB的存在使得C++成为一门危险的系统语言,但也正是它让C++的性能能够接近汇编。安全编程的核心原则是:避免任何UB。这需要开发者具备良好的编码规范和使用静态分析工具。例如,使用-Wall -Wextra -Wpedantic编译选项,启用UBSan(Undefined Behavior Sanitizer)进行运行时检测,使用Clang Static Analyzer、PVS-Studio等工具扫描代码。

现代C++引入了许多特性来减少UB风险。智能指针消除了大部分指针相关的UB;constexpr限制了一些动态行为;std::optional明确表达可能没有值;std::variant类型安全的联合体。C++20的std::bit_cast提供了安全位转换,避免违反严格别名规则。此外,使用span替代数组指针可以附带边界信息。

然而,某些UB无法完全消除,例如跨平台编程中的字节序问题、类型双关(type punning)。对于这些场景,应使用明确定义的行为,如memcpy、std::bit_cast或联合体(在C++中,联合体的活动成员规则严格,但编译器通常支持作为扩展)。

UB不仅影响正确性,还严重影响安全性。历史上许多高危漏洞(如Heartbleed、Stagefright)都与内存越界、释放后使用等UB相关。攻击者可以诱导程序进入UB状态,然后利用编译器优化产生的非预期行为进行攻击。因此,安全敏感系统(如浏览器内核、操作系统)会使用额外的防护机制(如ASan、CFI)来检测或阻止UB。

C++核心指南(C++ Core Guidelines)提供了大量避免UB的建议。例如,使用gsl::span代替指针和长度;使用gsl::owner明确所有权;使用RAII管理资源。遵循这些指南,结合自动化工具,可以将UB风险降到最低。

值得注意的是,某些UB在不同平台上可能有定义行为(例如x86上整数溢出回绕)。但这不代表可以依赖它,因为编译器优化可能会破坏假设。最佳实践是始终编写标准定义的程序,使用静态断言检查平台依赖https://dcdr.cn。

随着C++23和未来版本的演进,标准委员会逐步填补UB的灰色地带。例如,C++20将符号整数溢出从UB改为“实现定义”的提议被否决,但增加了检查溢出的一些工具函数(std::add_sat等)。安全配置文件(Safe Profiles)的提案旨在通过静态检查消除类内存安全的UB。

总之,C++的未定义行为既是性能之源,也是错误之渊。开发者必须正视它、理解它,并通过规范、工具和现代语言特性来规避它。

目录
相关文章
|
算法 Linux 开发者
CMake深入解析:打造高效动态链接库路径设置
CMake深入解析:打造高效动态链接库路径设置
1091 0
|
算法 计算机视觉
OpenCV(四十):图像分割—漫水填充
OpenCV(四十):图像分割—漫水填充
715 0
|
应用服务中间件 Linux nginx
Jetson 环境安装(四):jetson nano配置ffmpeg和nginx(亲测)之编译错误汇总
这篇文章是关于在Jetson Nano上配置FFmpeg和Nginx时遇到的编译错误及其解决方案的汇总。
594 4
|
机器学习/深度学习 算法 计算机视觉
旋转目标检测【1】如何设计深度学习模型
平常的目标检测是平行的矩形框,“方方正正”的;但对于一些特殊场景(遥感),需要倾斜的框,才能更好贴近物体,旋转目标检测来啦~
1834 0
|
传感器 定位技术
Ardupilot — EKF3使用光流室内定位代码梳理
Ardupilot — EKF3使用光流室内定位代码梳理
943 0
|
安全 编译器 C语言
NULL和nullptr到底是什么?它们的区别又是什么?
NULL和nullptr到底是什么?它们的区别又是什么?
|
定位技术 开发工具 C++
C++绘图库matplotlibcpp在Visual Studio中的配置方法
C++绘图库matplotlibcpp在Visual Studio中的配置方法
735 1
相机和livox激光雷达外参标定:在gazebo中搭建仿真场景
前两篇介绍了相机和livox激光雷达外参标定:ROS功能包的livox_camera_lidar_calibration 和使用方法. 具体链接: - [链接1](https://www.guyuehome.com/38522) - [链接2](https://www.guyuehome.com/38524) 本篇在gazebo中搭建可以模拟产生livox_camera_lidar_calibration功能包需要的数据的仿真场景.
相机和livox激光雷达外参标定:在gazebo中搭建仿真场景
|
传感器 人工智能 数据可视化
点云配准新方案!SuperLine3D:激光雷达点云中的自监督线分割和描述子提取(ECCV2022)
电线杆和建筑物的轮廓是城市道路上随处可见的物体,可为计算机视觉提供可靠的提示。为了重复提取它们作为特征并在离散的LiDAR帧之间实现关联以进行点云匹配。本文提出了一个用于LiDAR点云中3D线的基于学习的特征分割和描述子模型。
点云配准新方案!SuperLine3D:激光雷达点云中的自监督线分割和描述子提取(ECCV2022)
|
存储 算法 前端开发
LIO-SAM回环检测模块代码解析
LIO-SAM回环检测模块代码解析
1012 0
LIO-SAM回环检测模块代码解析

热门文章

最新文章

下一篇
开通oss服务