1

I came across this bug in some code running on a Blackfin 533 processor.

The first time Func() runs, fooStruct will contain garbage, but in the next iteration, the old value that was returned by getFoo() will by chance still be in fooStruct.foo.

FooStruct
{
    double foo;
    double bar;
};

void Func()
{
    FooStruct fooStruct;

    double bar = 123.4 / fooStruct.foo;

    fooStruct.foo = getFoo();
    fooStruct.bar = bar;
}

That means that the first time this runs, we are reading from an uninitialized variable, which is undefined behavior. What about the following iterations? Is that still undefined behavior? What sort of behavior can we expect to see when reading uninitialized variables on embedded processors?

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
Q-bertsuit
  • 3,223
  • 6
  • 30
  • 55
  • Could you not just initialise foo and bar, or add a boolean indicating when each iteration has safe values to use? – user997112 Mar 19 '18 at 10:27
  • Yes of course. The bug is easy to fix. My question is about what happens when this sort of thing go undetected. – Q-bertsuit Mar 19 '18 at 11:13
  • 1
    What happens depends on how you use foo. Consider that foo can take any value (from NaN to infinite, +0, -0 and other special cases). All code or result depending on the foo variable will have an unknown random value. In your example, if foo value is 0 you may go to Hardfault. – Julien Mar 19 '18 at 11:56
  • Do you want to fix the bug or to document it proper? (The latter may apply if this is in a software which operates in many devices on field, making reprogramming uneconomical). If you want to fix, then Bathsheba's answer applies, if you want to document, then you should possibly look at the disassembly to see what the compiled code does (that case whether it was undefined in C is moot as you have to describe the behaviour, which depends only on the binary what is actually in the ROM). – Jubatian Mar 26 '18 at 12:02

2 Answers2

7

One undefined behaviour has been encountered, the behaviour of that and all subsequent statements is undefined too.

Paradoxically, the behaviour of any statements prior to the undefined one are undefined too.

As for the sort of behaviour, asking to categorise undefined behaviour is not logical.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
4

Yes it is undefined, but the behaviour you observe is not necessarily surprising; it is just that the stack is reused and the reused space is not initialised, and you happened to have reused exactly the same stack location as the previous call. All memory has to contain something and if you call this function and it happens to re-use the same stack frame as a previous call, it will contain whatever was last left there.

For example if you call:

Func() ;
Func() :

It is not defined, but not unreasonable for the second call fooStruct.foo to contain the value left by the first call, because that is what would happen when the compiler takes no action to initialise the variable.

However if instead you had:

void Func2()
{
    int x = 0 ;
    int y = 0 ;
    Func() ;
}

Then called:

Func() ;
Func2() ;

The second call to Func() via Func2() would almost certainly place the local fooStruct.foo at a different address within the stack because of the stack frame for Func2, so would not then have the same value other then by coincidence. Moreover if the sequence were:

Func() ;
Func2() ;
Func() ;

The third call to Func() might use the same stack location as the first, but that space will probably have been modified by Func2() (because of the initialised variables), so likely you will no longer observe the same value in fooStruct.foo.

That is what uninitialised means; you get whatever happens to be there. And because when a variable goes out of scope, it is not generally modified, such values can "reappear" (and not necessarily in the same variable) - just because that is the simplest and most efficient implementation (i.e. to do nothing).

Clifford
  • 88,407
  • 13
  • 85
  • 165