Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this undefined behavior or a false positive warning?

Consider the following code:

class A {
private:
  int a;

public:
  A(int a) : a(a) { }
};

class B : public A {
private:
  int b;

  bool init() {
    b = 0;
    return true;
  }

public:
  // init() is a hack to initialize b before A()
  // B() : b(0), A(b) {} yields -Wreorder
  // B() : A((b = 0)) {} no warning (but this one doesn't work so well with non-pod (pointer) types)
  B() : A(init() ? b : 0) {}
};

Now trying to compile this code with clang...

$ clang++ test.cpp -fsyntax-only
test.cpp:19:20: warning: field 'b' is uninitialized when used here [-Wuninitialized]
B() : A(init() ? b : 0) {}
                 ^
1 warning generated.

GCC does not print any warnings, not even with -Wall -Wextra and -pedantic.

like image 585
Thomas Avatar asked May 09 '15 19:05

Thomas


2 Answers

It's undefined behavior. According to [class.base.init]:

In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes ...
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

b won't have been initialized by the time that the A base class is initialized. The assignment b = 0 is itself undefined behavior for the same reason - b hadn't been initialized yet when that is called. Its default constructor would still be called after A's constructor.

If you want ensure that b is initialized first, the typical approach is the base-from-member idiom:

struct B_member {
    int b;
    B_member() : b(0) { }
};

class B : public B_member, public A 
{
public:
    B() : A(b)  // B_member gets initialized first, which initializes b
                // then A gets initialized using 'b'. No UB here.
    { };
};
like image 64
Barry Avatar answered Oct 20 '22 15:10

Barry


In either case, calling a member function before base classes are initialized invokes undefined behavior. §12.6.2/16:

Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the typeid operator (5.2.8) or of a dynamic_cast (5.2.7). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined. [ Example:

class A {
public:
  A(int);
};

class B : public A {
  int j;
public:
  int f();
  B() : A(f()),  // undefined: calls member function
                 // but base A not yet initialized

  j(f()) { }    // well-defined: bases are all initialized
};

However, the access of and assignment to b itself is fine, since it has vacuous initialization and its lifetime starts as soon as storage is acquired for it (which happened long before the constructor call started). Hence

class B : public A {
private:
  int b;

public:
  B() : A(b=0) {}
};

is well-defined.

like image 33
Columbo Avatar answered Oct 20 '22 13:10

Columbo