What Is Indeterminate Behavior in C++ ? How Is It Different from Undefined Behavior

What is indeterminate behavior in C++ ? How is it different from undefined behavior?

EDIT 1: The last drafts of C11 and C++11 are available online here: C11 draft N1570 and C++11 draft n3242 if you don't have a copy of the final standards and wonderful what they look like. (Other adjustments to text appearance and some wording/grammar edits have been done.)

EDIT 2: Fixed all occurrences of "behaviour" to be "behavior" to match the standard.

Searching the C++11 and C11 standards there are no matches for indeterminate rule or undefined rule. There are terms like indeterminate value, indeterminately sequenced, indeterminate uninitialized, etc.

If talk of traps and exceptions seems weird in Norman Gray's answer, know that those terms do reflect the relevant definitions in Section 3 in the C11 standard.

C++ relies on C's definitions. Many useful definitions concerning types of behaviour can be found in C11's Section 3 (in C11). For example, indeterminate value is defined in 3.19.2. Do take note that C11's Section 2 (Normative References) provides other sources for additional terminology interpretation and Section 4 defines when cases such as undefined behavior occur as a result of not complying with the standard.

C11's section 3.4 defines behavior, 3.4.1 defines implementation-defined behavior, 3.4.2 defines locale-specific behavior, 3.4.3 defines undefined behavior, 3.4.4 defines unspecified behavior. For value (Section 3.19), there are implementation-defined value, indeterminate value, and unspecified value.

Loosely speaking, the term indeterminate refers to an unspecified/unknown state that by itself doesn't result in undefined behavior. For example, this C++ code involves an indeterminate value: { int x = x; }. (This is actually an example in the C++11 standard.) Here x is defined to be an integer first but at that point it does not have a well-defined value --then it is initialized to whatever (indeterminate/unknown) value it has!

The well-known term undefined behavior is defined in 3.4.3 in C11 and refers to any situation of a

nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

In other words undefined behavior is some error (in logic or state) and whatever happens next is unknown! So one could make an undefined [behavior] rule that states: avoid undefined behavior when writing C/C++ code! :-)

An indeterminate [behavior] rule would be to state: avoid writing indeterminate code unless it is needed and it does not affect program correctness or portability. So unlike undefined behavior, indeterminate behavior does not necessarily imply that code/data is erroneous, however, its subsequent use may or may not be erroneous --so care is required to ensure program correctness is maintained.

Other terms like indeterminately sequenced are in the body text (e.g., C11 5.1.2.3 para 3; C++11, section 1.9 para. 13; i.e., in [intro.executation]). (As you might guess, it refers an unspecified order of operational steps.)

IMO if one is interested in all of these nuances, acquiring both the C++11 and C11 standards is a must. This will permit one to explore to the desired level-of-detail needed with definitions, etc. If you don't have such the links provided herein will help you explore such with the last published draft C11 and C++11 standards.

Difference between uninitialized and indeterminate

I think your interpretation is close, but not exactly right.

uninitialized means the memory is reserved, but not filled with meaningful data. An easy example:

int* ptr;

All this does is reserve a piece of memory to contain the pointer, but since there is no int yet, the value of that pointer is not altered and contains a random garbage address to a meaningless location.

Another example:

class MyClass
{
int a;
int b;

MyClass() :
a(b) //Oops
b(5)
{ }
}

int b is not initialized yet when using it for a and, again, contains random garbage.


An indeterminate state is when the object has been initialized, but (probably because it is not intended to be used anymore) you don't know what's inside it.

Its behaviour is unpredictable, but not invalid (unless, of course, the documentation says it's invalid as well). For instance:

std::string a("text");
std::string b(std::move(a));

string a is now probably just an empty string. Accessing it will not cause undefined behavior, it will behave like any other empty string. Except you don't know for sure that it's empty.

For all you know, it might have replaced its char array with a reference to some static const char array saying "this is an indeterminate object why are you accessing me leave me alone.". Or some complicated series of seemingly random characters that is the result of internal optimizations. You can't know what is in it, and shouldn't use it. But when you do, it won't give you undefined behavior. Just useless behavior, on undefined data.


default-initialization and value-initialization are not part of your main question, you should ask for those in a separate question (assuming you can't find the answer on this site already).

Is reading an indeterminate value undefined behavior?

yes, formally an rvalue conversion of indeterminate value is UB (except for unsigned char, originally i wrote "and variants" but as i recall the formal caters to 1's complement signed char where possibly minus 0 could be used as trap value)

i'm too lazy to do the standard paragraph lookup for you, and also to lazy to care about downvotes for that

however, in practice only a problem on (1) archaic architectures, and perhaps (2) 64-bit systems.

EDIT: oops, i now seem to recall a blog posting and associated Defect Report about formal UB for accessing indeterminate char. so perhaps i'll have to actually check the standard, + search DRs. argh, it will have to be later then, now coffee!

EDIT2: Johannes Schaub was kind enough to provide this link to SO question where that UB for accessing char was discussed. So, that's where I remembered it from! Thanks, Johannes.

cheers & hth.,

Has C++ standard changed with respect to the use of indeterminate values and undefined behavior in C++14?

Yes, this change was driven by changes in the language which makes it undefined behavior if an indeterminate value is produced by an evaluation but with some exceptions for unsigned narrow characters.

Defect report 1787 whose proposed text can be found in N39141 was recently accepted in 2014 and is incorporated in the latest working draft N3936:

The most interesting change with respect to indeterminate values would be to section 8.5 paragraph 12 which goes from:

If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. — end note ]

to (emphasis mine):

If no initializer is specified for an object, the object is
default-initialized. 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 [expr.ass]). [Note: Objects with static or thread storage
duration are zero-initialized, see 3.6.2 [basic.start.init]. —end
note] 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 [basic.fundamental]) is produced by the evaluation of:

    • the second or third operand of a conditional expression (5.16 [expr.cond]),

    • the right operand of a comma (5.18 [expr.comma]),

    • the operand of a cast or conversion to an unsigned narrow character type (4.7 [conv.integral], 5.2.3 [expr.type.conv], 5.2.9
      [expr.static.cast], 5.4 [expr.cast]), or

    • a discarded-value expression (Clause 5 [expr]),


    then the result of the operation is an indeterminate value.

  • If an indeterminate value of unsigned narrow character type (3.9.1 [basic.fundamental]) is produced by the evaluation of the right
    operand of a simple assignment operator (5.17 [expr.ass]) 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 (3.9.1 [basic.fundamental]) 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.

and included the following example:

[ 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
}

end example ]

We can find this text in N3936 which is the current working draft and N3937 is the C++14 DIS.

Prior to C++1y

It is interesting to note that prior to this draft unlike C which has always had a well specified notion of what uses of indeterminate values were undefined C++ used the term indeterminate value without even defining it (assuming we can not borrow definition from C99) and also see defect report 616. We had to rely on the underspecified lvalue-to-rvalue conversion which in draft C++11 standard is covered in section 4.1 Lvalue-to-rvalue conversion paragraph 1 which says:

[...]if the object is uninitialized, a program that necessitates this conversion has undefined behavior.[...]


Footnotes:

  1. 1787 is a revision of defect report 616, we can find that information in N3903

Is using any indeterminate value undefined or just those stored in objects with automatic storage?

I am using C11 revision here:

The definitions from the standard are:

indeterminate value

either an unspecified value or a trap representation

trap representation

an object representation that need not represent a value of the object type

unspecified value

Unspecified valid value of the relevant type where this International Standard imposes no
requirements on which value is chosen in any instance


An unspecified value is a valid value of the relevant type and as such it does not cause undefined behaviour. Using a trap representation will.


But why this wording exists in the standard is that the excerpt enables compilers to issue diagnostics, or reject programs that use the value of uninitialized local variables yet still stay standard-compliant; because there are types of which it is said that they cannot contain trap representations in memory, so they'd always be having unspecified value there in their indeterminate state. This applies to for example unsigned char. And since using an unspecified value does not have undefined behaviour then the standard does not allow one to reject such a program.

Additionally, say an unsigned char normally does not have a trap representation... except, IIRC there are computer architectures where a register can be set to "uninitialized", and reading from a register in such an architecture will trigger a fault. Thus even if an unsigned char does not really have trap representations in memory, on this architecture it will with cause a hardware fault with 100 % probability, if it is of automatic storage duration and compiler decides to store it in a register and it is still uninitialized at the time of the call.

What happens to uninitialized variables in C/C++?


Q.1) What happens if an uninitialized variable is used in say an operation? Will it crash/ will the code fail to compile?

Many compilers will warn you about code that improperly uses the value of an uninitialized variable. Many compilers have an option that says "treat warnings as errors". So depending on the compiler you're using and the option flags you invoke it with, the code might fail to compile, although we can't say that it will fail to compile.

If the code does compile, and you try to run it, it's obviously impossible to predict what will happen. In most cases the variable will start out containing an "indeterminate" value. Whether that indeterminate value will cause your program to work correctly, or work incorrectly, or crash, is anyone's guess. If the variable is an integer and you try to do some math on it, you'll probably just get a weird answer. But if the variable is a pointer and you try to indirect on it, you're quite likely to get a crash.

It's often said that uninitialized local variables start out containing "random garbage", but that can be misleading, as evidenced by the number of people who post questions here pointing out that, in their program where they tried it, the value wasn't random, but was always 0 or was always the same. So I like to say that uninitialized local variables never start out holding what you expect. If you expected them to be random, you'll find that (at least on any given day) they're repeatable and predictable. But if you expect them to be predictable (and, god help you, if you write code that depends on it), then by jingo, you'll find that they're quite random.

Whether use of an uninitialized variable makes your program formally undefined turns out to be a complicated question. But you might as well assume that it does, because it's a case you want to avoid just as assiduously as you avoid any other dangerous, undefined behavior.

See this old question and this other old question for more (much more!) information on the fine distinctions between undefined and indeterminate behavior in this case.

Q.2) Will C and C++ standards differ in how they treat an uninitialized variable?

They might differ. As I alluded to above, and at least in C, it turns out that not all uses of uninitialized local variables are formally undefined. (Some are merely "indeterminate".) But the passages quoted from the C++ standards by other answers here make it sound like it's undefined there all the time. Again, for practical purposes, the question probably doesn't matter, because as I said, you'll want to avoid it no matter what.

Q.3) Regarding similar queries, how and where can I find an 'official' answer? Is it practical for an amateur to look up the C and C++ standards?

It is not always easy to obtain copies of the standards (let alone official ones, which often cost money), and the standards can be difficult to read and to properly interpret, but yes, given effort, anyone can obtain, read, and attempt to answer questions using the standards. You might not always make the correct interpretation the first time (and you may therefore need to ask for help), but I wouldn't say that's a reason not to try. (For one thing, anyone can read any document and end up not making the correct interpretation the first time; this phenomenon is not limited to amateur programmers reading complex language standards documents!)

Is reading values of unitialized object yields Undefined Behavior


Where does this reasoning fail?

One failure is here

Since the representation of int is never a trap

int can have trap representations.

The only type that can't have trap representations is unsigned char

But there is also this part in the standard describing undefined behavior (from draft n1570):

J.2 Undefined behavior

...

An lvalue designating an object of automatic storage duration that could have been
declared with the register storage class is used in a context that requires the value
of the designated object, but the object is uninitialized. (6.3.2.1).



Related Topics



Leave a reply



Submit