Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fun with uninitialized variables and compiler (GCC)

The section §3.9.1/6 from the C++ Standard says,

Values of type bool are either true or false.

Now consider this code,

void f(bool b)
{
    switch(b) //since b is bool, it's value can be either true or false!
    {
        case true: cout << "possible value - true";  break;
        case false: cout << "possible value - false"; break;
        default: cout << "impossible value";
    }
}
int main()
{
    bool b; //note : b is uninitialized
    f(b);
    return 0;
}

Compile F:\workplace>g++ test.cpp -pedantic

Run. Output :

impossible value

Unexpected output? Well, not really, as the Standard reads in the footnote of §3.9.1/6 that:

Using a bool value in ways described by this International Standard as “undefined,” such as by examining the value of an uninitialized automatic object, might cause it to behave as if it is neither true nor false.

So no matter how many times I compile and run this program, I get the same output : impossible value. However, if I change it a bit - removing the function f() from the picture, and write the switch block in main() itself:

int main()
{
    bool b; //note : b is uninitialized
    switch(b) //since b is bool, it's value can be either true or false!
    {
        case true: cout << "possible value - true";  break;
        case false: cout << "possible value - false"; break;
        default: cout << "impossible value";
    }
    return 0;
}

Then I compile and run this program, I don't get impossible value as output; no matter how many times I repeat this, I never get impossible value.

I'm just curious to know why this sudden change in the behavior of uninitialized bool?

Well, from the language perspective it's clear : the behavior is undefined.I understand that. I also understand the compiler is free to do anything. From the compiler perspective, however, this seems very interesting to me. What could the compiler (i.e GCC) possibly do in each case and why?

I'm using : g++ (GCC) 4.5.0 - MinGW, on Windows 7 Basic, 64-bit OS.

like image 224
Nawaz Avatar asked Feb 02 '11 19:02

Nawaz


2 Answers

I'm just curious to know why this sudden change in the behavior of uninitialized bool?

Disassemble the code and see what the compiler’s doing.

My guess: since the value is now only used locally, the compiler optimizes it away completely. Since the behaviour is undefined anyway, the compiler can safely just assume any value, e.g. false. This is a pretty obvious optimization since the value of b is constant as far as the compiler is concerned, and the whole logic of the switch is redundant. So why put it in the executable?

(The important point here is really that b is only ever used locally in the second code, and that in turn will trigger more optimizations even in unoptimized code. The first code has to be inlined before the compiler can do any such optimizations, or the code paths have to be traced which isn’t trivial).

like image 54
Konrad Rudolph Avatar answered Nov 03 '22 13:11

Konrad Rudolph


Just today I came across a version of this bug. I offer my experience here in case it's enlightening to anyone else.

I had some code which boiled down to

if(!(a == b && c.d())) { do_something(); }

The bug I was chasing was that do_something() was happening, wrongly. Yet a was definitely equal to b and c.d() was, it seemed, returning true.

As I was tracking this down, I temporarily added these test printouts:

if(  a == b && c.d() ) printf("yes\n"; else printf("no\n");
if(!(a == b && c.d())) printf("noo\n"; else printf("yess\n");

To my surprise this printed yes and noo, which confirmed both why do_something was happening, and that something very strange was going on.

It turned out that method d() was something like

bool whatever::d() {
    return _successful;
}

But _successful was uninitialized. When I printed out its value, it was 236, which is why earlier I had said "c.d() was, it seemed, returning true."

I didn't inspect the assembly code, but I'm guessing that under some circumstances, gcc was testing whether it was nonzero or not, but under others, it was just testing the low-order bit.

Properly initializing _successful made the bug go away. (It had been uninitialized for ten years, since an earlier programmer first wrote method d(). Yet the bug hadn't manifested until a few months ago. This is why, sometimes, Software Is Hard.)

like image 1
Steve Summit Avatar answered Nov 03 '22 15:11

Steve Summit