【C++20】编译期检测所有未定义行为和内存泄露

局部变量未初始化

编译器规定,编译期是不能触发未定义行为的。加上constexpr

#include <cstdio>
#include <cmath>
#include <array>

//不能在 constexpr 函数中使用非 constexpr 变量
constexpr int func() {
    
    
    int i;//这里的局部变量未初始化
    return i;
}

int main() {
    
    
    constexpr int i = func(); //constexpr 要求函数在编译期能求值,所以 func() 必须返回一个编译时常量
    printf("%d\n", i);
    return 0;
}

函数没有返回值

#include <cstdio>

constexpr int func(int i) {
    
    
    if (i >= 0)
        return i;
}

int main() {
    
    
    constexpr int i = func(-1);
    printf("%d\n", i);
    return 0;
}

在这里插入图片描述

如果在使用函数时,没有使用constexpr,则

#include <cstdio>

constexpr int func(int i) {
    
    
    if (i >= 0)
        return i;
}

int main() {
    
    
    int i = func(-1);
    printf("%d\n", i);
    return 0;
}

在这里插入图片描述

函数中移位操作检测

#include <cstdio>

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

int main() {
    
    
    constexpr int _1 = func(3);   // 2^3 = 8
    constexpr int _2 = func(1);   // 2^1 = 2
    constexpr int _3 = func(-1);  // 超出范围,返回 0
    constexpr int _4 = func(-3);  // 超出范围,返回 0

    printf("%d %d %d %d\n", _1, _2, _3, _4);
    return 0;
}

在这里插入图片描述

左移的量必须在0-31之间(int 只有32bit),否则触发未定义行为

#include <cstdio>

constexpr int func(int i) {
    
    
    if (i >= 0 && i <= 31)
        return 1 << i;  // 左移操作,计算 2^i
    return 0;  // 确保所有路径都有返回值
}

int main() {
    
    
    constexpr int _1 = func(3);   // 2^3 = 8
    constexpr int _2 = func(1);   // 2^1 = 2
    constexpr int _3 = func(-1);  // 超出范围,返回 0
    constexpr int _4 = func(-3);  // 超出范围,返回 0

    printf("%d %d %d %d\n", _1, _2, _3, _4);
    return 0;
}

检测数组越界

#include <cstdio>

constexpr int func(int i) {
    
    
    constexpr int a[32] = {
    
    }; // constexpr 变量,保证编译期计算
    // if (i < 0 || i >= 32) {   // 判断 i 是否在 0~31 之间
    //     return 0;  // 确保所有路径都有返回值
    // }
    return a[i];  // 返回数组对应元素的值
}

int main() {
    
    
    constexpr int _1 = func(32);   // 超出范围,返回 0
    constexpr int _2 = func(31);   // 数组索引 31
    constexpr int _3 = func(1);    // 数组索引 1
    constexpr int _4 = func(0);    // 数组索引 0
    constexpr int _5 = func(-1);   // 超出范围,返回 0
    constexpr int _6 = func(-31);  // 超出范围,返回 0
    constexpr int _7 = func(-32);  // 超出范围,返回 0

    printf("%d\n", _7);
    return 0;
}

在这里插入图片描述

数组指针加法

数组指针加法,超出了数组的范围也会报错,clang++会直接报错,gcc只在解引用的时候报错

#include <cstdio>

constexpr int func(int i) {
    
    
    constexpr int a[32] = {
    
    }; // 确保数组在编译期可用
    // if (i < 0 || i >= 32) {   // 限制 i 在合法范围内
    //     return 0;  // 超出范围返回 0
    // }
    a+i;
    return 0;  // 返回数组对应索引的值
}

int main() {
    
    
    constexpr int _0 = func(33);   // 超出范围,返回 0
    constexpr int _1 = func(32);   // 超出范围,返回 0
    constexpr int _2 = func(31);   // 访问数组索引 31
    constexpr int _3 = func(1);    // 访问数组索引 1
    constexpr int _4 = func(0);    // 访问数组索引 0
    constexpr int _5 = func(-1);   // 超出范围,返回 0
    constexpr int _6 = func(-31);  // 超出范围,返回 0
    constexpr int _7 = func(-32);  // 超出范围,返回 0
    constexpr int _8 = func(-33);  // 超出范围,返回 0

    printf("%d\n", _8);
    return 0;
}

在这里插入图片描述

#include <cstdio>

constexpr int func(int i) {
    
    
    constexpr int a[32] = {
    
    }; // 确保数组在编译期可用
    // if (i < 0 || i >= 32) {   // 限制 i 在合法范围内
    //     return 0;  // 超出范围返回 0
    // }
    int* p = a+i;
    return *p;  // 返回数组对应索引的值
}

int main() {
    
    
    constexpr int _0 = func(33);   // 超出范围,返回 0
    constexpr int _1 = func(32);   // 超出范围,返回 0
    constexpr int _2 = func(31);   // 访问数组索引 31
    constexpr int _3 = func(1);    // 访问数组索引 1
    constexpr int _4 = func(0);    // 访问数组索引 0
    constexpr int _5 = func(-1);   // 超出范围,返回 0
    constexpr int _6 = func(-31);  // 超出范围,返回 0
    constexpr int _7 = func(-32);  // 超出范围,返回 0
    constexpr int _8 = func(-33);  // 超出范围,返回 0

    printf("%d\n", _8);
    return 0;
}

在这里插入图片描述

std::end()

#include <iostream>
#include <iterator> // std::begin, std::end

int main() {
    
    
    int a[32] = {
    
    };  // 初始化数组,所有元素默认为 0

    std::cout << "数组元素:" << std::endl;
    for (int* it = std::begin(a); it != std::end(a); ++it) {
    
    
        std::cout << *it << " ";  // 输出数组的每个元素
    }
    std::cout << std::endl;

    return 0;
}

std::begin(a) 返回数组 a 的首地址(即 &a[0])。
std::end(a) 返回数组 a 尾后位置(即 &a[32],超出数组范围的一个位置)。
it != std::end(a) 避免访问 a[32] 这个非法位置。

使用 std::begin() 和 std::end() 初始化 std::vector

#include <iostream>
#include <vector>
#include <iterator> // std::begin, std::end

int main() {
    
    
    int a[32] = {
    
    1, 2, 3, 4, 5}; // 只有前 5 个元素初始化,其他默认为 0

    std::vector<int> vec(std::begin(a), std::end(a)); // 用数组范围初始化 vector

    std::cout << "Vector 中的元素:" << std::endl;
    for (int num : vec) {
    
    
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

std::fill()

#include <iostream>
#include <algorithm> // std::fill

int main() {
    
    
    int a[32];  // 未初始化,包含垃圾值

    std::fill(a, a + 32, 7); // 把数组的所有元素填充为 7

    for (int num : a) {
    
    
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

std::fill(a, a + 32, 7); 表示:

  • 起始位置:a(即 &a[0])
  • 结束位置:a + 32(即 &a[32],超出数组范围的一个位置)
  • 填充值:7

该操作将 a[0] 到 a[31] 全部填充为 7。

结合 std::begin() 和 std::end()

#include <iostream>
#include <algorithm> // std::fill
#include <iterator>  // std::begin, std::end

int main() {
    
    
    int a[32];

    std::fill(std::begin(a), std::end(a), -1); // 用 -1 填充整个数组

    for (int num : a) {
    
    
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

如果想填充部分数组,例如 只填充前 10 个元素:

#include <iostream>
#include <algorithm>

int main() {
    
    
    int a[32] = {
    
    }; // 默认初始化为 0

    std::fill(a, a + 10, 5); // 只填充前 10 个元素

    for (int num : a) {
    
    
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

int数溢出与unsigned int数回环,max到min

eg,int数溢出

#include <cstdio>
#include <cmath>
#include <array>

constexpr int add(int i) {
    
    
    return i + 1;
}

int main() {
    
    
    constexpr int result = add(2147483647);
    printf("%d\n", result);  // 输出将是 2147483648(但根据你的系统的 int 大小,可能会发生溢出)
    return 0;
}

在这里插入图片描述

eg:unsigned int数回环,max到min

#include <cstdio>
#include <cmath>
#include <array>

constexpr unsigned int add(unsigned int i) {
    
    
    return i + 1;  // 完成加法操作
}

int main() {
    
    
    constexpr unsigned int int_1 = add(0xffffffff);  // 0xffffffff 表示最大值
    constexpr unsigned int int_2 = add(0xfffffffe);  // 0xfffffffe 表示最大值减1

    printf("int_1: %u\n", int_1);  // 使用 %u 输出无符号整数
    printf("int_2: %u\n", int_2);  // 输出 int_2 的结果

    return 0;
}

在这里插入图片描述

死循环

下面的会死循环,int越界是未定义行为

#include <cstdio>

int main() {
    
    
    for (int t = 1; t != 0; t++) {
    
    
        // 循环体为空,循环一直执行直到 t 溢出为 0
    }
    return 0;
}

不会死循环,unsigned int越界,则回到起始位置—多用

#include <cstdio>

int main() {
    
    
    for (unsigned int t = 1; t != 0; t++) {
    
    
        // 循环体为空,循环一直执行直到 t 溢出为 0
    }
    return 0;
}

new和delete不匹配

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p = new int[n];  // 动态分配内存
    // delete[] p;  // 正确的删除方式是 delete[],因为我们使用的是 new[]
    delete p;
    return 0;
}

int main() {
    
    
    constexpr int result1 = func(1);
    int result2 = func(2);
    return 0;
}

在这里插入图片描述

new一个0大小,但是解引用试图访问

#include <cstdio>

constexpr  int func(int n) {
    
    
    int* p = new int[n]{
    
    };
    int ret = *p;
    delete[] p;
    return ret;
}

int main() {
    
    
    constexpr int result1 = func(0);
    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d %d\n", result1, result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p = new int[n]{
    
    };
    int ret = p[0];
    delete[] p;
    return ret;
}

int main() {
    
    
    constexpr int result1 = func(0);
    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d %d\n", result1, result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述

编译期检测内存没有释放

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p = new int[n]{
    
    };
    return p[0];
}

int main() {
    
    

    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d\n", result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述
解决办法

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p = new int[n]{
    
    };
    struct guard{
    
    
        int* a;
        constexpr ~guard() noexcept{
    
    
            delete [] a;
        }
    }_{
    
    p};
    
    
    return p[0];
}

int main() {
    
    

    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d\n", result2, result3);  // 输出结果
    return 0;
}

delete nullptr不会出错

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p =nullptr;
    delete p;
    
    return 0;
}

int main() {
    
    

    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d\n", result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述

double free

#include <cstdio>

constexpr int func(int n) {
    
    
    int* p =new int{
    
    };
    delete p;
    delete p;
    
    return p[0];
}

int main() {
    
    

    constexpr int result2 = func(1);
    constexpr int result3 = func(2);

    printf("%d %d\n", result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述

编译期检测vector

编译期检测vector,长度为0,缺访问其0号元素,越界了

#include <cstdio>
#include <vector>

constexpr int func(int n) {
    
    
    std::vector<int> a(n);  
    /*
    	int*p  = nullptr;
    	p+4;
    */
    //报错等价于空指针+0,off by 0可以,off by 1就不信了
    return a[0];  
    //a.front()等价于a[0]
    /*
		std::vector<int> a;
		a.reserve(2);//reserve是未初始化内存
		a.reserve(2);//resize用0初始化

		a.clear();// clear() 只清空 vector 中的元素,底层内存(capacity)依然保持
	
	*/
}

int main() {
    
    
    constexpr int result1 = func(0);  // 调用 func(0)
    constexpr int result2 = func(1);  // 调用 func(1)
    constexpr int result3 = func(2);  // 调用 func(2)

    printf("result1: %d, result2: %d, result3: %d\n", result1, result2, result3);  // 输出结果
    return 0;
}

在这里插入图片描述

访问一个已经删除了的指针

#include <cstdio>
#include <vector>

constexpr int func() {
    
    
    std::vector<int> a{
    
    1,2,3,4};
    auto it = a.begin();  // 获取 vector 的第一个元素的迭代器
    a.push_back(42);  // 向 vector 中添加一个值

    return *it;  // 返回迭代器指向的元素值
}

int main() {
    
    
    constexpr int result = func();  // 调用 func() 并获取结果
    printf("%d\n", result);  // 输出结果
    return 0;
}

在这里插入图片描述
解决办法:先push

#include <cstdio>
#include <vector>

constexpr int func() {
    
    
    std::vector<int> a{
    
    1,2,3,4};

    a.push_back(42);  // 向 vector 中添加一个值
    auto it = a.begin();  // 获取 vector 的第一个元素的迭代器
    return *it;  // 返回迭代器指向的元素值
}

int main() {
    
    
    constexpr int result = func();  // 调用 func() 并获取结果
    printf("%d\n", result);  // 输出结果
    return 0;
}

解决办法,先reserve预留一些空间

#include <cstdio>
#include <vector>

constexpr int func() {
    
    
    std::vector<int> a{
    
    1,2,3,4};
    a.reserve(5);
    auto it = a.begin();  // 获取 vector 的第一个元素的迭代器
    a.push_back(42);  // 向 vector 中添加一个值
    
    return *it;  // 返回迭代器指向的元素值
}

int main() {
    
    
    constexpr int result = func();  // 调用 func() 并获取结果
    printf("%d\n", result);  // 输出结果
    return 0;
}

参考