【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);
      |                              ^

参考

相关推荐
lexusv8ls600h1 天前
探索 C++20:C++ 的新纪元
c++·c++20
扣得君1 天前
C++20 Coroutine Echo Server
运维·服务器·c++20
lexusv8ls600h1 天前
C++20 中最优雅的那个小特性 - Ranges
c++·c++20
不爱学英文的码字机器2 天前
C++20新特性详解
算法·c++20
年轻的古尔丹3 天前
【C++ 20进阶(2):属性 Attribute】
c++20·属性·新特性·c++20新特性·c++属性
一只小松许️3 天前
C++20协程详解
开发语言·php·c++20
sun0077008 天前
MISRA C++ 2023 编码标准&规范
c++20
飞翔的薄荷11 天前
C++20 时间转本地时间,时间转字符串以及字符串转时间的方法
算法·c++20
何曾参静谧17 天前
「C/C++」C++20 之 #include<ranges> 范围
c语言·c++·c++20
年轻的古尔丹18 天前
【C++ 20进阶(1):模块导入 import】
c++20·c++ module·c++ import·c++ export·c++ 模块