C ++程序员应该知道的所有常见的未定义行为是什么?

C ++程序员应该知道的所有常见的未定义行为是什么?

比如说:

a[i] = i++;


指针

  • 解引用NULL指针
  • 取消引用大小为零的“新”分配返回的指针
  • 使用指向其生命周期已结束的对象的指针(例如,堆栈分配的对象或已删除的对象)
  • 解引用尚未明确初始化的指针
  • 执行指针算术,该算术可在数组的边界之外(上方或下方)生成结果。
  • 将指针解引用超出数组末尾的位置。
  • 将指针转换为不兼容类型的对象
  • 使用memcpy复制重叠的缓冲区。
  • 缓冲区溢出

  • 读取或写入对象或数组的偏移量为负值或超出该对象的大小(堆栈/堆溢出)
  • 整数溢出

  • 有符号整数溢出
  • 评估未经数学定义的表达式
  • 左移值为负值(右移负值是实现定义的)
  • 将值移位的数量大于或等于数中的位数(例如, int64_t i = 1; i <<= 72未定义)
  • 类型,演员和常量

  • 将数值转换为目标类型无法表示的值(直接或通过static_cast)
  • 在明确赋值之前使用自动变量(例如, int i; i++; cout << i;
  • 在收到信号时使用除volatilesig_atomic_t以外的任何类型对象的值
  • 尝试在其生命周期中修改字符串文字或其他任何常量对象
  • 在预处理过程中将窄字符串连接起来
  • 功能和模板

  • 不从一个返回值函数返回一个值(直接或者从一个try-block中流出)
  • 同一实体(类,模板,枚举,内联函数,静态成员函数等)的多个不同定义
  • 模板实例化中的无限递归
  • 使用不同的参数调用一个函数,或者调用函数被定义为使用的参数和链接。
  • OOP

  • 级联的静态存储持续时间对象的破坏
  • 分配给部分重叠对象的结果
  • 在静态对象的初始化过程中递归地重新输入一个函数
  • 从其构造函数或析构函数中虚拟函数调用对象的纯虚函数
  • 提到尚未构建或已被破坏的物体的非静态成员
  • 源文件和预处理

  • 非空的源文件,不以换行符结尾,或以反斜杠结尾(在C ++ 11之前)
  • 反斜杠后跟一个字符,它不是字符或字符串常量中指定转义代码的一部分(这是在C ++ 11中实现定义的)。
  • 超出实现限制(嵌套块的数量,程序中的函数数量,可用堆栈空间...)
  • 无法用long int表示的预处理器数值
  • 预处理指令位于函数式宏定义的左侧
  • #if表达式中动态生成定义的标记
  • 待分类

  • 在销毁具有静态存储持续时间的程序期间调用exit

  • 函数参数的评估顺序是未指定的行为 。 (这不会让你的程序崩溃,爆炸,或订购比萨...不像未定义的行为 。)

    唯一的要求是,在调用函数之前,必须全面评估所有参数。


    这个:

    // The simple obvious one.
    callFunc(getA(),getB());
    

    可以等同于:

    int a = getA();
    int b = getB();
    callFunc(a,b);
    

    或这个:

    int b = getB();
    int a = getA();
    callFunc(a,b);
    

    它可以是; 这取决于编译器。 结果可能很重要,这取决于副作用。


    编译器可以自由地对表达式的评估部分进行重新排序(假定其含义不变)。

    从原来的问题来看:

    a[i] = i++;
    
    // This expression has three parts:
    (a) a[i]
    (b) i++
    (c) Assign (b) to (a)
    
    // (c) is guaranteed to happen after (a) and (b)
    // But (a) and (b) can be done in either order.
    // See n2521 Section 5.17
    // (b) increments i but returns the original value.
    // See n2521 Section 5.2.6
    // Thus this expression can be written as:
    
    int rhs  = i++;
    int lhs& = a[i];
    lhs = rhs;
    
    // or
    int lhs& = a[i];
    int rhs  = i++;
    lhs = rhs;
    

    双重检查锁定。 而且容易犯一个错误。

    A* a = new A("plop");
    
    // Looks simple enough.
    // But this can be split into three parts.
    (a) allocate Memory
    (b) Call constructor
    (c) Assign value to 'a'
    
    // No problem here:
    // The compiler is allowed to do this:
    (a) allocate Memory
    (c) Assign value to 'a'
    (b) Call constructor.
    // This is because the whole thing is between two sequence points.
    
    // So what is the big deal.
    // Simple Double checked lock. (I know there are many other problems with this).
    if (a == null) // (Point B)
    {
        Lock   lock(mutex);
        if (a == null)
        {
            a = new A("Plop");  // (Point A).
        }
    }
    a->doStuff();
    
    // Think of this situation.
    // Thread 1: Reaches point A. Executes (a)(c)
    // Thread 1: Is about to do (b) and gets unscheduled.
    // Thread 2: Reaches point B. It can now skip the if block
    //           Remember (c) has been done thus 'a' is not NULL.
    //           But the memory has not been initialized.
    //           Thread 2 now executes doStuff() on an uninitialized variable.
    
    // The solution to this problem is to move the assignment of 'a'
    // To the other side of the sequence point.
    if (a == null) // (Point B)
    {
        Lock   lock(mutex);
        if (a == null)
        {
            A* tmp = new A("Plop");  // (Point A).
            a = tmp;
        }
    }
    a->doStuff();
    
    // Of course there are still other problems because of C++ support for
    // threads. But hopefully these are addresses in the next standard.
    
    链接地址: http://www.djcxy.com/p/73201.html

    上一篇: What are all the common undefined behaviours that a C++ programmer should know about?

    下一篇: What is the difference between a sequence point and operator precedence?