Skip to content

Latest commit

 

History

History
765 lines (660 loc) · 27.4 KB

all_section.md

File metadata and controls

765 lines (660 loc) · 27.4 KB

[lex]

[lex.phases]

[lex.pptoken]

  • A preprocessing token is the minimal lexical element of the language in translation phases 3 through 6. … If a or a " character matches the last category, the behavior is undefined. [lex.pptoken]/p2
    #define STR_START "
    #define STR_END "
    
    int puts(const char *);
    
    int main() {
      puts(STR_START hello world STR_END);
    }
    • Examples live
    • Discussion
    • Rationale
      • Preprocessing token are generated during phase 3 so string and character literals as well and header names are identified at this point. both ‘ and “ would only be valid as part of one of these tokens, any left over after this point perhaps as part of a macro would not be valid since they could not tokenized as needed anymore. Macros are expanded as part of phase 4.

[lex.string]

  • The effect of attempting to modify a string literal is undefined
    • Examples:
    const char *p1 = "hello world\n"; 
    char *p2 = const_cast<char*>(p1) ;   // const_cast is already suspicious
      
    p2[0] = 'm' ;
    • Examples live
    • Rationale *[lex.string]p8

      Ordinary string literals and UTF-8 string literals are also referred to as narrow string literals. A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration (6.6.4).

[basic]

[basic.def.odr]

  • There can be more than one definition of a class type (Clause 12), enumeration type (10.2), inline function with external linkage (10.1.6), inline variable with external linkage (10.1.6), class template (Clause 17), non-static function template (17.6.6), concept (17.6.8), static data member of a class template (17.6.1.3), member function of a class template (17.6.1.1), or template specialization for which some template parameters are not specified (17.8, 17.6.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then ... If the definitions of D do not satisfy these requirements, then the behavior is undefined.

[basic.life]

  • A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression ([expr.delete]) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

[basic.indet]

  • If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases
    int f(bool b) {
      unsigned char c;
      unsigned char d = c; // OK, d has an indeterminate value
      int e = d; // undefined behavior
      return b ? d : 0; // undefined behavior if b is true
    }
    • Rationale
      • WG14 Defect report 260
      • WG14 Defect report 451
      • Tl;DR; We have two case one in which using an indeterminate value is undefined behavior and this is because many type can have trap representations and using these value are undefined behavior. In the case of narrow character types the underlying values and type representation are one to one and therefore we don’t have a trap representation but they do retain their indeterminateness.

[basic.start]

[expr]

[expr.pre]

  • Signed integer overflow/underflow is undefined behavior

[conv.double]

  • Converting floating point value to type that cannot represent the value is undefined behavior even for float

[conv.fpint]

  • Converting floating point value to an integral that cannot represent the value is undefined behavior

[expr.call]

  • Calling a function through an expression whose function type is different from the function type of the called function’s definition results in undefined behavior
    int f_c(int);
    
    using c1 = int(*)(int);
    using c2 = int(*)(int,int);
    
    int f(c2 func) {
     return func(1,2);
    }
    
    int main() {
      f(reinterpret_cast<c2>(f_c));
    }

[expr.static.cast]

  • If the object of type “cv1 B” is actually a base class subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined.

    struct B {};
    struct D1:B {};
    struct D2:B {};
    
    void f() {
     D1 d;
     B &b = d;
     static_cast<D2&>(b);
    }
  • Setting an enum to a value outside the range of enumerators is undefined behavior

    enum A {e1=1, e2};
    
    void f() {
      enum A a=static_cast<A>(4);   
    } 
  • Down-casting to the wrong derived type is undefined behavior

    struct B {};
    struct D1:B {};
    struct D2:B {};
    
    void f() {
      B* bp = new D1;
      static_cast<D2*>(bp);
    }

[expr.delete]

  • Using array delete on the result of a single object new expression and vice versa is undefined behavior

    int *x = new int;
    delete [] x;
  • If the dynamic type differs from the static type of the object being deleted that is undefined behavior

    int *p = new int;
    float *f = reinterpret_cast<float*>(p);
    delete f;
  • Deleting and incomplete type and the class turns out to have a non-trivial destructor is undefined behavior

    struct A;
    
    void f(A *p) {
      delete p;
    }
    
    struct A {~A(){}};

[expr.mptr.oper]

  • If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined

    struct B{};
    struct D:B{int x;};
    
    void f(){
     B *b= new B;
     D *d=static_cast<D*>(b);
     int D::* p=&D::x;
     (*d).*p=1;
    }
  • If the second operand is the null member pointer value (7.3.12), the behavior is undefined.

    struct S {
     int i;
    };
    
    void f()
    {
     S cs;
     int S::* pm = nullptr;
     cs.*pm = 88;
    }

[expr.mul]

[expr.add]

  • Incrementing pointer beyond one past the end of an array is undefined behavior

    static const int arrs[10]{};
    
    void f() {
     const int* y = arrs + 11;
    }
  • Subtracting pointers that are not part of the same array is undefined behavior

    void f() {
     int x;
     int y;
     int *p1=&x;
     int *p2=&y;
     std::ptrdiff_t off = p1-p2;
    }

[expr.shift]

[expr.ass]

  • Overlap in an assignment expression must be exact and the objects must have the same type
    int x=1;
    char *c=reinterpret_cast<char*>(&x);
    x = *c;

[stmt.stmt]

[stmt.return]

[stmt.dcl]

  • Recursively entering declaration of a block scope static variable during initialization is undefined behavior
    int foo(int i) {
    static int s = foo(2*i);
    return i+1;
    }

[dcl.dcl]

[dcl.type.cv]

  • Attempting to modify a const object is undefined behavior

    int bar() {
    const int x=1;
    
    int *p = const_cast<int*>(&x);
    *p = 2;
    
    return *p;
    }
  • Accessing a volatile value through a non-volatile is undefined behavior

    void f() {
    volatile int x=0;
    int &y=const_cast<int&>(x);
    std::cout << y;
    }

    -Examples live

[dcl.attr.contract.syn]

  • In contracts side effects in a predicate to an object whose lifetime did not begin and end within the evaluation of the predicate are undefined behavior
    int min = -42;
    constexpr int g(int x) {
      /* ... */
      [[assert: ++min > 0]]; // undefined behavior
      /* ... */
      return 0;
    }

[dcl.attr.contract.syn]

  • if a postcondition odr-uses a non-reference parameter in its predicate and the function body makes direct or indirect modifications of the value of that parameter, the behavior is undefined.
    int f(int x)
    [[ensures r: r == x]]
    {
      return ++x; // UB
    }

[dcl.attr.contract.check]

[dcl.attr.noreturn]

  • A function declared noreturn eventually returns it is undefined behavior
    [[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0
    if (i > 0)
      throw "positive";
    }

[class]

[class.mfct.non-static]

  • Calling a non-static member function of a class with an object that is not of that type is undefined behavior
    struct X {
      int x=1;
      int f() { return x;}
    };
    
    struct A {int x=3;};
    
    int f(X*x) {
      return x->f();
    }

[class.dtor]

  • Explicit destructor call for an object not of the type is undefined behavior

    struct X {};
    
    void f() {
      X *x=nullptr;
      x->~X();
    }
  • Invoking the destructor for an object once its lifetime has ended is undefined behavior

    struct A{
       ~A(){}
    };
    
    int main() {
      A a;
      a.~A(); // Destructor will be invoked again at scope exit invoking UB
    }

[class.union]

  • Accessing a non-active union member is undefined behavior
    union Y { float f; int k; };
    void g() {
     Y y = { 1.0f }; // OK, y.f is active union member (10.3)
     int n = y.k;
    }

[class.abstract]

  • Calling a virtual function from a constructor or destructor in an abstract class is undefined behavior
    struct B {
     virtual void f()=0;
     B() { f();}
    };
    
    struct D : B{
        void f() override { }
    };

[class.base.init]

  • Calling a member function before all baes are initialized is undefined behavior
    struct B {
      B(int);
    };
    
    struct D : public B {
      int f();
      D() : B(f()) {}
    };

[class.cdtor]

  • For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior

    struct W { int j; };
    struct X : public virtual W { };
    
    struct Y {
     int* p;
     X x; 
     Y() : p(&x.j) { // undefined, x is not yet constructed
      }
    };
  • To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior

    struct A { };
    struct B : virtual A { };
    struct C : B { };
    struct D : virtual A { D(A*); };
    struct X { X(A*); };
    
    struct E : C, D, X {
      E() : D(this), // undefined: upcast from E* to A* might use path E* ! D* ! A*
                         // but D is not constructed
    
                        // “D((C*)this)” would be defined: E* ! C* is defined because E() has started,
                       // and C* ! A* is defined because C is fully constructed
    
        X(this) {} // defined: upon construction of X, C/B/D/A sublattice is fully constructed. 
    };
  • To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

    struct A {
      int x;
    };
    
    void f() {
      A a;
      a.~A();
      int *p=&a.x; // Destruction completed so computing the pointer is undefined behavior
    }
  • If the virtual function call uses an explicit class member access (7.6.1.4) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

    struct V {
      virtual void f();
      virtual void g();
    };
    
    struct A : virtual V {
      virtual void f();
    };
    
    struct B : virtual V {
      virtual void g(); 
      B(V*, A*);
    };
    
    struct D : A, B {
      virtual void f();
      virtual void g();
      D() : B((A*)this, this) { }
    };
    
    B::B(V* v, A* a) {
      f(); // calls V::f, not A::f
      g(); // calls B::g, not D::g
      v->g(); // v is base of B, the call is well-defined, calls B::g
      a->f(); // undefined behavior, a’s type not a base of B. 
    }
  • If the operand of typeid refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor’s class nor one of its bases, the behavior is undefined

    struct V {
     virtual void f();
    };
    
    struct A : virtual V { };
    struct B : virtual V {
     B(V*, A*);
    };
    
    struct D : A, B {
     D() : B((A*)this, this) { }
    };
    
    B::B(V* v, A* a) {
      typeid(*this); // type_info for B.
      typeid(*v); // well-defined: *v has type V, a base of B yields type_info for B
      typeid(*a); // undefined behavior: type A not a base of B
      dynamic_cast<B*>(v); // well-defined: v of type V*, V base of B results in B*
      dynamic_cast<B*>(a); // undefined behavior, a has type A*, A not a base of B
    }
  • If the operand of the dynamic_cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor’s own class or one of its bases, the dynamic_cast results in undefined behavior

      struct V {
     virtual void f();
    };
    
    struct A : virtual V { };
    struct B : virtual V {
     B(V*, A*);
    };
    
    struct D : A, B {
     D() : B((A*)this, this) { }
    };
    
    B::B(V* v, A* a) {
      typeid(*this); // type_info for B.
      typeid(*v); // well-defined: *v has type V, a base of B yields type_info for B
      typeid(*a); // undefined behavior: type A not a base of B
      dynamic_cast<B*>(v); // well-defined: v of type V*, V base of B results in B*
      dynamic_cast<B*>(a); // undefined behavior, a has type A*, A not a base of B
    }