【C++20】编译期检测所有未定义行为undefined behavior和内存泄漏(不借助编译选项以及任何外部工具)

本文主要是介绍【C++20】编译期检测所有未定义行为undefined behavior和内存泄漏(不借助编译选项以及任何外部工具),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、未定义行为Undefined Behavior(UB)
    • 1.返回一个未初始化的局部变量的值
    • 2.数组越界访问
    • 3.有符号数的常量表达式溢出
    • 4.new与delete
    • 5.vector
    • 6.空指针解引用
  • 参考

一、未定义行为Undefined Behavior(UB)

在C++中,未定义行为(Undefined Behavior)指的是程序的行为没有定义、不可预测或不符合C++标准的情况。当程序中存在未定义行为时,编译器和运行时环境不会对其进行任何保证,可能会导致程序产生意外的结果。

以下是一些常见的导致未定义行为的情况:

  • 访问未初始化的变量:如果使用未初始化的变量,其值是不确定的,可能包含任意的垃圾值。

  • 数组越界访问:当访问数组时,如果超出了数组的有效索引范围,将导致未定义行为。

  • 空指针解引用:当将空指针用作指针解引用,即访问其指向的内存区域时,将导致未定义行为。

  • 除以零:在C++中,除以零是一种未定义行为,可能导致程序崩溃或产生无效的结果。

  • 使用已释放的内存:如果使用已释放的内存,或者使用指向已释放内存的指针,将导致未定义行为。

  • 栈溢出:当递归调用或者使用过多的局部变量导致栈空间耗尽时,将导致未定义行为。

  • 多个线程之间的竞争条件:如果多个线程同时访问并修改共享数据而没有适当的同步机制,可能会导致未定义行为。

编译器使用x86_64 gcc13.2
C++版本:-std=c++20

1.返回一个未初始化的局部变量的值

UB写法:

#include <cstdio>int func()
{int i;return i;
}int main()
{int i = func();printf("%d\n",i);return 0;
}

编译及运行结果:

Program returned: 0
Program stdout
0

使用编译期constexpr检测UB:

#include <cstdio>constexpr int func()
{int i;return i;
}int main()
{constexpr int i = func();printf("%d\n",i);return 0;
}

编译及运行结果:

<source>: In function 'constexpr int func()':
<source>:6:9: error: uninitialized variable 'i' in 'constexpr' function6 |     int i;|         ^
<source>: In function 'int main()':
<source>:12:27: error: 'constexpr int func()' called in a constant expression12 |     constexpr int i = func();|                       ~~~~^~

通过constexpr 进行UB检测:

#include <cstdio>constexpr int func(int i)
{if (i >= 0)return 0;}int main()
{constexpr int _1 = func(1);constexpr int _2 = func(-1);return 0;
}

编译及运行:

Could not execute the program
Compiler returned: 1
Compiler stderr
<source>: In function 'int main()':
<source>:14:28:   in 'constexpr' expansion of 'func(-1)'
<source>:14:31: error: 'constexpr' call flows off the end of the function14 |     constexpr int _2 = func(-1);|

编译期使用constexpr 检测UB 写法(关于移位的例子):

  • int类型的数据(4Bytes),最多只能移动31bit
#include <cstdio>constexpr int func(int i)
{return 1 << i;
}int main()
{constexpr int _1 = func(32);constexpr int _2 = func(-1);printf("%d\n",_1);return 0;
}

编译及运行:


<source>: In function 'int main()':
<source>:11:28:   in 'constexpr' expansion of 'func(32)'
<source>:6:14: error: right operand of shift expression '(1 << 32)' is greater than or equal to the precision 32 of the left operand [-fpermissive]6 |     return 1 << i;|            ~~^~~~
<source>:12:28:   in 'constexpr' expansion of 'func(-1)'
<source>:6:14: error: right operand of shift expression '(1 << -1)' is negative [-fpermissive]

2.数组越界访问

编译期通过constexpr检测UB写法:

#include <cstdio>constexpr int func(int i)
{int a[32]={};return a[i];
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(32);printf("%d\n",_1);return 0;
}

编译及运行:

<source>: In function 'int main()':
<source>:14:28:   in 'constexpr' expansion of 'func(32)'
<source>:8:15: error: array subscript value '32' is outside the bounds of array 'a' of type 'int [32]'8 |     return a[i];|            ~~~^
<source>:6:9: note: declared here6 |     int a[32]={};|         ^

使用编译期constexpr检测UB:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>constexpr int func(int i)
{int a[32]={};// std::fill(a, a+2,0);//编译期检测:直接访问数组外的数据并不会出错,但是在return中使用就会出错int* b= a+32;*b;return *std::end(a);
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(32);printf("%d\n",_1);return 0;
}

编译以及运行:


<source>: In function 'int main()':
<source>:20:28:   in 'constexpr' expansion of 'func(0)'
<source>:20:30: error: array subscript value '32' is outside the bounds of array type 'int [32]'20 |     constexpr int _1 = func(0);|                              ^
<source>:21:28:   in 'constexpr' expansion of 'func(32)'
<source>:21:31: error: array subscript value '32' is outside the bounds of array type 'int [32]'21 |     constexpr int _2 = func(32);

编译期使用constexpr检测UB行为:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>constexpr int func(int i)
{int a[32]={};return *(char*)a;
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(32);printf("%d\n",_1);return 0;
}

编译及运行:

  • 如果不使用constexpr就无法检测出这个指针强潜在的UB行为
  • C语言可以跨平台,如果Host主机是大端的,而不是小端的,那么强转后的地址一定是数据的低位吗?

<source>: In function 'int main()':
<source>:16:28:   in 'constexpr' expansion of 'func(0)'
<source>:11:13: error: a reinterpret_cast is not a constant expression11 |     return *(char*)a;|             ^~~~~~~~
<source>:17:28:   in 'constexpr' expansion of 'func(32)'
<source>:11:13: error: a reinterpret_cast is not a constant expression

3.有符号数的常量表达式溢出

编译期使用constexpr检测UB行为:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int i)
{ return i + 1;
}int main()
{constexpr int _1 = func(2147483647);constexpr int _2 = func(-2147483648);printf("%d\n",_1);return 0;
}

编译及运行:


<source>: In function 'int main()':
<source>:15:28:   in 'constexpr' expansion of 'func(2147483647)'
<source>:15:39: error: overflow in constant expression [-fpermissive]15 |     constexpr int _1 = func(2147483647);|                                       ^

4.new与delete

使用constexpr检测UB行为:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int n)
{ int *p=new int[n];delete p;return 0;
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译及运行:

<source>: In function 'int main()':
<source>:19:28:   in 'constexpr' expansion of 'func(1)'
<source>:11:12: error: non-array deallocation of object allocated with array allocation11 |     delete p;|            ^
<source>:10:21: note: allocation performed here10 |     int *p=new int[n];|                     ^

使用constexpr检测UB行为:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int n)
{ int *p=new int[n]{};delete[] p;return p[0];
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译及运行:


<source>: In function 'int main()':
<source>:19:28:   in 'constexpr' expansion of 'func(1)'
<source>:19:30: error: use of allocated storage after deallocation in a constant expression19 |     constexpr int _2 = func(1);|                              ^
<source>:10:23: note: allocated here10 |     int *p=new int[n]{};|                       ^

使用constexpr检测UB行为:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int n)
{ int *p=new int[n]{};// delete[] p;return p[0];
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译以及运行:

<source>: In function 'int main()':
<source>:10:23: error: 'func(1)' is not a constant expression because allocated storage has not been deallocated10 |     int *p=new int[n]{};|                       ^

使用智能指针在consexpr中自动析构

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexpr
//return 之后才会调用所有成员的析构函数
constexpr int func(int n)
{ int *p=new int[n]{};struct guard{int* p;constexpr ~guard() noexcept{delete[] p;}}_v(p);return p[0];
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

使用constexpr检测UB:

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int n)
{ int *p=new int[n]{};delete[] p;delete[] p;return p[0];
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译及运行:

<source>: In function 'int main()':
<source>:19:28:   in 'constexpr' expansion of 'func(1)'
<source>:12:14: error: deallocation of already deallocated storage12 |     delete[] p;|        

delete 空指针不会造成UB

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>// #define constexprconstexpr int func(int n)
{ int *p=nullptr;delete p;return 0;
}int main()
{// constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

5.vector

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>// #define constexprconstexpr int func(int n)
{ std::vector<int> v(n);return v[0];
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译及运行:

In file included from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/vector:66,from <source>:5:
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/stl_vector.h: In function 'int main()':
<source>:17:28:   in 'constexpr' expansion of 'func(0)'
<source>:12:15:   in 'constexpr' expansion of 'v.std::vector<int>::operator[](0)'
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/stl_vector.h:1126:41: error: dereferencing a null pointer1126 |         return *(this->_M_impl._M_start + __n);|                 ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

std::vector v(n);并不将所有成员都初始化为0,vector的resize()方法可以初始化vector内部的成员都初始化为0

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>// #define constexprconstexpr int func(int n)
{ std::vector<int> v(n);v.reserve(2);v.resize(20);return v[0];
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(1);// std::vector<int> v(0);// v.reserve(20);// v.resize(2);// printf("%u\n", v.size());// printf("%u\n", v.capacity());printf("%d\n",_2);return 0;
}

6.空指针解引用

#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>// #define constexprconstexpr int func(int n)
{ int* p=nullptr;return *p;
}int main()
{constexpr int _1 = func(0);constexpr int _2 = func(1);printf("%d\n",_2);return 0;
}

编译及运行:

<source>: In function 'int main()':
<source>:17:28:   in 'constexpr' expansion of 'func(0)'
<source>:17:30: error: dereferencing a null pointer17 |     constexpr int _1 = func(0);|                              ^
<source>:18:28:   in 'constexpr' expansion of 'func(1)'
<source>:18:30: error: dereferencing a null pointer18 |     constexpr int _2 = func(1);|                              ^

参考

  • 【C++20】编译期检测所有未定义行为和内存泄漏,不借助任何外部工具

这篇关于【C++20】编译期检测所有未定义行为undefined behavior和内存泄漏(不借助编译选项以及任何外部工具)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/579722

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.