7

Here is a simple test case that compiles without any warning. Looks like a common mistake but clang, gcc and visual studio doesn't emit warning in this case. Why?

class Image {
  private:
    int width, height;
    int* array;
  public:
    Image(int _width, int _height);
    void crashTest();
};
Image::Image(int _width, int _height)
{
  array = new int[width * height];
               // ^^^^^   ^^^^^^ this is wrong
               // I expect a warning here e.g.: 'width is uninitialized here'
  width = _width;
  height = _height;
}
void Image::crashTest()
{
  for (int x = 0; x < width; ++x)
  {
    for (int y = 0; y < height; ++y)
      array[x + y * width] = 0;
  }
}
int main()
{
  const int ARRAY_SIZE = 1000;
  Image image(ARRAY_SIZE, ARRAY_SIZE);
  image.crashTest();
  return 0;
}

e.g.:

g++ -Wall -Wextra -O2 -Wuninitialized test.cpp
clang++ -Wall -Wextra -O2 -Wuninitialized test.cpp

gives me no warning

  • I don't see the problem? Aren't primitive types guaranteed to be default-initialized? – jaredready May 29 '14 at 16:03
  • 1
    @ResidentBiscuit No, that is the problem. Well, yes, technically they are *default-initialized*, but that means no initialization is performed. So in plain English, they aren't initialized. – juanchopanza May 29 '14 at 16:05
  • But, they are initialized. Just default initialized. The compiler _shouldn't_ complain about this. – jaredready May 29 '14 at 16:06
  • 4
    @ResidentBiscuit reading from them is undefined behaviour. It would be nice if the compiler emitted a warning, but it doesn't have to. – juanchopanza May 29 '14 at 16:07
  • You're using flags, you must mention them in the question! – Nawaz May 29 '14 at 16:17
  • 3
    It is a QoI-Issue, and obviously the ctor is not special-cased enough to catch reading default-initialised non-class types (those where default-initializing equals doing nothing). – Deduplicator May 29 '14 at 16:25

2 Answers2

9

A follow-up to this old question: You can get the warning you are looking for with g++ by enabling warnings from "Effective C++" with -Weffc++. This will complain about member variables that are not explicitly initialised.

This is possibly too aggressive in that it will also complain about class members with default constructors that are not explicitly initialised.

I have not seen an equivalent option for Clang -- I agree a warning about classes with uninitialised primitive data members would be very useful.

janm
  • 17,976
  • 1
  • 43
  • 61
7

Short Answer

As pointed out in the comments, reading from an uninitialized variable is undefined behavior. Compilers are not obligated by the standard to provide a warning for this.

(In fact, as soon as your program expresses undefined behavior, the compiler is effectively released from any and all obligations...)

From section [defns.undefined] of the standard (emphasis added):

undefined behavior

behavior for which this International Standard imposes no requirements

[ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. —end note ]


Long Answer

This can be a difficult situation for a compiler to detect (and if it does detect it, it's difficult to inform the user about it in some useful way).

Your code only exhibits undefined behavior because it's trying to read from uninitialized member variables width and height. The fact that they're member variables is one of the things that can make this situation tricky to diagnose.

With local variables, the static analysis involved in detecting this can be relatively straightforward (not all the time, mind you).

For example, it's very easy to see the problem here:

    int foo()
    {
        int a;
        int b = 0;

        return a + b; // Danger! `a` hasn't been initialized!
    }

How about in this scenario:

    int foo(int& a)
    {
        int b = 1;

        return a + b; // Hmm... I sure hope whoever gave me `a` remembered to initialize it first 
    }

    void bar()
    {
        int value;
        int result = foo(value); // Hmm... not sure if it matters that value hasn't been initialized yet
    }

As soon as we start dealing with variables whose scope extends beyond a single block, it's very difficult to detect whether or not a variable has been initialized.

Now, relating this back to the problem at hand (your question): the variables width and height are not local to the constructor - they could have been initialized outside the constructor.

For example:

    Image::Image(int _width, int _height)
    {
      Initialize();

      array = new int[width * height]; // Maybe these were initialized in `Initialize`...

      width = _width;
      height = _height;
    }

    Image::Initialize()
    {
        width = 0;
        height = 0;
    }

Should the compiler emit a warning in this scenario?

After some cursory analysis we can conclusively say "no, it shouldn't warn", because we can see that the Initialize method does indeed initialize the member variables in question.

But what if Initialize delegates this to another method MoreInitialize()? And that method delegates it to another method YetEvenMoreInitialize? This begins to look like a problem that we can't reasonably expect the compiler to solve.

Lilshieste
  • 2,744
  • 14
  • 20
  • I think this kind of error is too common to ignore it. – Industrial-antidepressant May 29 '14 at 16:39
  • 1
    I completely agree. The same can be said of a lot of scenarios that exhibit undefined behavior. Personally, I'm surprised that none of the big 3 compilers emits a warning for this. (My guess is that checking for this type of situation is a little harder than it seems to us.) – Lilshieste May 29 '14 at 16:48
  • 1
    @Industrial-antidepressant After thinking about the problem a little more, it became a lot more apparent why compilers wouldn't emit a warning here. I've edited my answer accordingly. – Lilshieste May 29 '14 at 18:06
  • @Lilshieste: Specifically, what if `Initialize` is in a runtime DLL? Then it's clearly _impossible_ for the compiler to detect if the variable was initialized or not. – Mooing Duck May 29 '14 at 18:10
  • @MooingDuck Didn't even think about that. Thanks for pointing it out! – Lilshieste May 29 '14 at 18:11
  • Downvoting because the argument "this is hard because a trickier case is hard" is invalid. I see little reason to expect detecting this case to be difficult, since the only opaque call is to `operator new`. I particularly dislike the Short Answer, because "it's legal" is not a justification, and using it as one is a historic cause of major problems with C++. – Veedrac Oct 20 '17 at 11:49
  • Note that gcc gives up very quickly: `int a; a+=1;` gives a warning (with `-Wall`), but `int a; for(int i=0;i<10;i++) a+=1;` does not give a warning, no matter how many flags and optimizations are applied. – Marijn Feb 08 '18 at 13:45