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

文章目录

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

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

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

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

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

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

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

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

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

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

编译器使用x86_64 gcc13.2

C++版本:-std=c++20

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

UB写法:

cpp 复制代码
#include <cstdio>


int func()
{
    int i;
    return i;
}

int main()
{
    int i = func();
    printf("%d\n",i);

    return 0;
}

编译及运行结果:

bash 复制代码
Program returned: 0
Program stdout
0

使用编译期constexpr检测UB:

cpp 复制代码
#include <cstdio>


constexpr int func()
{
    int i;
    return i;
}

int main()
{
    constexpr int i = func();
    printf("%d\n",i);

    return 0;
}

编译及运行结果:

bash 复制代码
<source>: In function 'constexpr int func()':
<source>:6:9: error: uninitialized variable 'i' in 'constexpr' function
    6 |     int i;
      |         ^
<source>: In function 'int main()':
<source>:12:27: error: 'constexpr int func()' called in a constant expression
   12 |     constexpr int i = func();
      |                       ~~~~^~

通过constexpr 进行UB检测:

cpp 复制代码
#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;
}

编译及运行:

bash 复制代码
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 function
   14 |     constexpr int _2 = func(-1);
      |

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

  • int类型的数据(4Bytes),最多只能移动31bit
cpp 复制代码
#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;
}

编译及运行:

bash 复制代码
<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写法:

cpp 复制代码
#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;
}

编译及运行:

bash 复制代码
<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 here
    6 |     int a[32]={};
      |         ^

使用编译期constexpr检测UB:

cpp 复制代码
#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;
}

编译以及运行:

bash 复制代码
<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行为:

cpp 复制代码
#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主机是大端的,而不是小端的,那么强转后的地址一定是数据的低位吗?
bash 复制代码
<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 expression
   11 |     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行为:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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;
}

编译及运行:

bash 复制代码
<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行为:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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;
}

编译及运行:

bash 复制代码
<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 allocation
   11 |     delete p;
      |            ^
<source>:10:21: note: allocation performed here
   10 |     int *p=new int[n];
      |                     ^

使用constexpr检测UB行为:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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;
}

编译及运行:

bash 复制代码
<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 expression
   19 |     constexpr int _2 = func(1);
      |                              ^
<source>:10:23: note: allocated here
   10 |     int *p=new int[n]{};
      |                       ^

使用constexpr检测UB行为:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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;
}

编译以及运行:

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

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

cpp 复制代码
#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:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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;
}

编译及运行:

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

delete 空指针不会造成UB

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>

// #define constexpr

constexpr 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

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>

// #define constexpr

constexpr 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;
}

编译及运行:

bash 复制代码
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 pointer
 1126 |         return *(this->_M_impl._M_start + __n);
      |                 ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

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

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>

// #define constexpr

constexpr 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.空指针解引用

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <array>
#include <algorithm>
#include <vector>

// #define constexpr

constexpr 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;
}

编译及运行:

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

参考

相关推荐
吃不饱的得可可1 天前
C++20常用新特性
c++20
hggngx548h13 天前
有哪些C++20特性可以在Dev-C++中使用?
开发语言·c++·c++20
R&L_2018100115 天前
C++20之三路比较运算符
c++20·c++ 新特性
buvsvdp50059ac16 天前
如何在Visual Studio中启用C++17或C++20?
c++·c++20·visual studio
TiAmo zhang18 天前
现代C++的AI革命:C++20/C++23核心特性解析与实战应用
c++·人工智能·c++20
m0_7482480218 天前
C++20 协程:在 AI 推理引擎中的深度应用
java·c++·人工智能·c++20
落羽的落羽20 天前
【C++】现代C++的新特性constexpr,及其在C++14、C++17、C++20中的进化
linux·c++·人工智能·学习·机器学习·c++20·c++40周年
kyle~23 天前
CPU调度---协程
java·linux·服务器·数据库·c++20
charlie1145141912 个月前
精读C++20设计模式:行为型设计模式:中介者模式
c++·学习·设计模式·c++20·中介者模式
charlie1145141912 个月前
理解C++20的革命特性——协程引用之——利用协程做一个迷你的Echo Server
网络·学习·socket·c++20·协程·epoll·raii