未定义行为总结

  1. 解引用空指针,例如解引用空的this指针、解引用空的函数指针

    下面的代码尽管能正常运行,但c->fun()等价于(*c).fun(),也就是对空指针解引用了

    #include <iostream>
    struct C
    {
        void fun(){
            std::cout<<"fun"<<std::endl;
        }
    };
    
    int main(int argc, char *argv[])
    {
        C* c = nullptr;
        c->fun();//fun
        return 0;
    }
    
  2. 解引用end迭代器

    因为end是指向容器后面的真空区域

    std::vector<int> v = {1,2};
    int* begin = &*v.begin();//正确
    int* end = &*v.end();//错误,未定义行为
    int* end = v.data()+v.size();//正确写法
    
  3. 有符号整型溢出是未定义行为,而无符号整型允许溢出

    #include <iostream>
    #include <climits>
    
    int main(int argc, char *argv[])
    {
        int a = INT_MAX;
        int b = a+1;//有符号整型溢出并不能保证回环(x86中才会回环),它是未定义行为
        std::cout<<b<<std::endl;//-2147483648
        unsigned int c = UINT_MAX;
        unsigned int d = c+3;//无符号整型允许溢出,会回环
        std::cout<<d<<std::endl;//2
        return 0;
    }
    
  4. 左移溢出(left shift)

    int i = 1<<32;
    unsigned int j =1<<32;
    
  5. 未写返回值是未定义行为,程序直接崩溃,但不显示错误

    cmake中这样写将未写返回值的警告转为错误
    target_compile_options(main PRIVATE -Werror=return-type)

  6. 读取未初始化的变量、已析构的对象

    从语法上来说,移动过的对象是可以访问的,但不要这样做
    下面代码在gcc中s为空,但在msvc中不清空。string字符串小于15字节时,小字符串优化将其存放在union结构体的local_buff中,因此移动和拷贝操作等价

    std::string s = "hello";
    std::string s2 = std::move(s);
    std::cout<<s<<std::endl;
    
  7. memcpy函数的dst和src都不能为空指针、不能重叠

    因为其中使用了__restrict关键字,告诉编译器该指针与其它指针是相互独立的,不会重叠,便于编译器进行优化

    void fun(int* p1, int* p2){
        *p1 = 2;
        *p2 = 3;//可能p2指向与p1相同
        std::cout<<*p1+*p2<<std::endl;//5或6
    }
    void fun2(int* __restrict p1, int* __restrict p2){
        *p1 = 2;
        *p2 = 3;
        std::cout<<*p1+*p2<<std::endl;//等价于直接返回5
    }
    

    如果确实需要进行就地移动,那就使用memmove(dst, src, size_t)

  8. T类型的指针必须对齐到alignof(T),构造未对齐的指针属于未定义行为

    malloc产生的指针只保证对齐到max_align_t,而new能保证对齐到alignof(T)

    #include <iostream>
    struct alignas(64) A//alignas是一个对齐说明符,用于指定变量或类型的最小对齐要求
    {
        int i;
        char j;
    };
    int main(int argc, char *argv[])
    {
        A* a1 = (A*)malloc(sizeof(A));//错误
        A* a2 = new A;//正确
        return 0;
    }
    
    //错误,buf只是大小为4,没有对齐到4字节,不能直接作为int*使用
    char buf[sizeof(int)];
    int* p = (int*)buf;
    //正确
    alignas(alignof(int)) char buf[sizeof(int)];
    int* p = (int*)buf;
    
  9. 多个线程访问同一个对象,其中有一个是写操作,是未定义行为

  10. 死锁也是未定义行为。

    多个线程对多个锁的上锁顺序不同就会死锁。可以使用std::lock(m1, m2)、std::unlock(m1,m2)来上锁和解锁 来避免
    重复加锁导致死锁是未定义行为:改用recursive_mutex或使用适当的条件变量

    std::mutex m;
    m.lock();
    bool res = m.try_lock();//重复加锁是未定义行为,即使是try_lock上锁
    std::cout<<res<<std::endl;//不一定
    m.unlock();
    
  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值