Home » C++ » Consequences of uninitialised variables: int vs unsigned char

Consequences of uninitialised variables: int vs unsigned char

Posted by: admin January 4, 2018 Leave a comment

Questions:

I saw the following example on cppreference.com

int x;     // OK: the value of x is indeterminate
int y = x; // undefined behavior

Here, int y = x; is undefined behavior because x is uninitialized.

But,

unsigned char c;     // OK: the value of c is indeterminate
unsigned char d = c; // OK: the value of d is indeterminate

Here, unsigned char d = c; is indeterminate behavior, but unsigned char c; is also an uninitialized variable.

So, Why is the value of unsigned char d indeterminate?

Answers:

Online references like cppreference.com are good up to a point. But it is known that sometimes errors or misinterpretations do occasionally slip through. So when dealing with such oddities, it is always a good thing to go to the official C++ standard.

N3936

§8.5 Initializers [dcl.init]

12 […] When storage for an object with automatic or dynamic storage
duration is obtained, the object has an indeterminate value , and if
no initialization is performed for the object, that object retains an
indeterminate value until that value is replaced (5.17). […] If an
indeterminate value is produced by an evaluation, the behavior is
undefined except in the following cases:

  • If an indeterminate value of unsigned narrow character type (3.9.1) is produced by the evaluation of

    • […]
    • the operand of a cast or conversion to an unsigned narrow character type (4.7, 5.2.3, 5.2.9, 5.4)

    • […]

    then the result of the operation is an indeterminate value.

  • If an indeterminate value of unsigned narrow character type is produced by the evaluation of the right operand of a simple assignment
    operator (5.17) whose first operand is an lvalue of unsigned narrow
    character type, an indeterminate value replaces the value of the
    object referred to by the left operand

  • If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when
    initializing an object of unsigned narrow character type, that object
    is initialized to an indeterminate value.

Example:

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
}

So (to my big surprise) the standard backs this up.

As for why, the most likely reason can be also found in the standard:

§3.9.1 Fundamental types [basic.fundamental]

1 […] For unsigned narrow character types, all possible bit patterns
of the value representation represent numbers. These requirements do
not hold for other types


As a side note, I just realized this can be used by an evil interviewer:

Q. Can you in a well-defined behavior change the valid value of an object to an undetermined value? If yes, how?

A.

unsigned char ind;
unsigned char x = 24;
x = ind; // x had a valid value, now x has an indetermined value

Questions:
Answers:

From the page you referenced: assigning from an indeterminate value is undefined behavior except

If the indeterminate value of unsigned narrow character type or std::byte is assigned to another variable with unsigned narrow character type or std::byte (the value of the variable becomes indeterminate, but the behavior is not undefined)

I believe this is because default initialization may place any combination of bits into the variable and while the standard guarantees that an unsigned narrow character type may take on values represented by every possible bit pattern, there is no such guarantee for other types.

Questions:
Answers:

From the linked page:

Use of an indeterminate value obtained by default-initializing a non-class variable of any type is undefined behavior […] except in the following cases:

if the indeterminate value of unsigned narrow character type or std::byte is used to initialize another variable with unsigned narrow character type or std::byte;

unsigned char is an unsigned narrow character, so this is one of the exceptions where UB does not occur.

Questions:
Answers:

Two related useful features of C, which carried into C++, are:

  1. Objects can be copied by copying all of the individual bytes contained therein.

  2. Structure-type objects can be safely copied in their entirety even when some of the objects therein do not hold defined values, provided that no attempt is made to read the undefined portions or copies thereof outside the context of whole-structure copying or individual-byte access.

On most platforms, there’s no particular reason why the same guarantees could not and should not be extended to other types as well, but the authors of the C Standard only sought to define guarantees that should be applicable on all platforms, and the authors of the C++ Standards have simply followed the C++ behaviors.