682

What is undefined behavior (UB) in C and C++? What about unspecified behavior and implementation-defined behavior? What is the difference between them?

6

10 Answers 10

506

Undefined behavior is one of those aspects of the C and C++ language that can be surprising to programmers coming from other languages (other languages try to hide it better). Basically, it is possible to write C++ programs that do not behave in a predictable way, even though many C++ compilers will not report any errors in the program!

Let's look at a classic example:

#include <iostream>
    
int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

The variable p points to the string literal "hello!\n", and the two assignments below try to modify that string literal. What does this program do? According to the C++ standard, [lex.string] note 4, it invokes undefined behavior:

The effect of attempting to modify a string literal is undefined.

I can hear people screaming "But wait, I can compile this no problem and get the output yellow" or "What do you mean undefined, string literals are stored in read-only memory, so the first assignment attempt results in a core dump". This is exactly the problem with undefined behavior. Basically, the standard allows anything to happen once you invoke undefined behavior (even nasal demons). If there is a "correct" behavior according to your mental model of the language, that model is simply wrong; The C++ standard has the only vote, period.

Other examples of undefined behavior include

[intro.defs] also defines undefined behavior's two less dangerous brothers, unspecified behavior and implementation-defined behavior:

implementation-defined behavior    [defns.impl.defined]

behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

unspecified behavior    [defns.unspecified]

behavior, for a well-formed program construct and correct data, that depends on the implementation

[Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this document. — end note]

undefined behavior    [defns.undefined]

behavior for which this document imposes no requirements

[Note: Undefined behavior may be expected when this document 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). [...] — end note]

What can you do to avoid running into undefined behavior? Basically, you have to read good C++ books by authors who know what they're talking about. Avoid internet tutorials. Avoid bullschildt.

Sign up to request clarification or add additional context in comments.

22 Comments

It's a weird fact that resulted from the merge that this answer only covers C++ but this question's tags includes C. C has a different notion of "undefined behavior": It will still require the implementation to give diagnostic messages even if behavior is also stated to be undefined for certain rule violations (constraint violations).
@Benoit It is undefined behavior because the standard says it's undefined behavior, period. On some systems, indeed string literals are stored in the read-only text segment, and the program will crash if you try to modify a string literal. On other systems, the string literal will indeed appear change. The standard does not mandate what has to happen. That's what undefined behavior means.
@FredOverflow, Why does a good compiler allow us to compile code that gives undefined behavior? Exactly what good can compiling this kind of code give? Why didn't all good compilers give us a huge red warning sign when we are trying to compile code that gives undefined behavior?
@Pacerier There are certain things that are not checkable at compile time. For example it is not always possible to guarantee that a null pointer is never dereferenced, but this is undefined.
@Celeritas, undefined behavior can be non-deterministic. For example, it is impossible to know ahead of time what the contents of uninitialized memory will be, eg. int f(){int a; return a;}: the value of a may change between function calls.
|
122

Well, this is basically a straight copy-paste from the C standard:

3.4.1 1 implementation-defined behavior unspecified behavior where each implementation documents how the choice is made

2 EXAMPLE An example of implementation-defined behavior is the propagation of the high-order bit when a signed integer is shifted right.

3.4.3 1 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

2 NOTE Possible 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).

3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.

3.4.4 1 unspecified behavior use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance

2 EXAMPLE An example of unspecified behavior is the order in which the arguments to a function are evaluated.

19 Comments

What's the difference between implementation-defined and unspecified behaviour?
@Zolomon: Just like it says: basucally the same thing, except that in case of implementation-defined the implementation is requred to document (to guarantee) what exactly is going to happen, while in case of unspecified the implementation is not required to document or guarantee anything.
@Celeritas: Hyper-modern compilers can do better than that. Given int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; } a compiler can determine that since all means of invoking the function that don't launch the missiles invoke Undefined Behavior, it can make the call to launch_missiles() unconditional.
@northerner As the quote states, unspecified behavior is usually restricted to a limited set of possible behaviors. In some cases you might even come to conclusion that all of these possibilities are acceptable in the given context, in which cases unspecified behavior is not a problem at all. Undefined behavior is completely unrestricted (e.b. "the program may decide to format your hard drive"). Undefined behavior is always a problem.
@northerner : Nope. The key point of in case of "unspecified behavior" is the fact that the set of possible behaviors is actually specified and restricted. And typically it is an easily overseeable set. What's unspecified is which specific possibility (out of that set) will be chosen by the compiler. The implicit distinctions is that the set of posibilities is typically restgricted and [fairly] platform-independent. In case of undefined behavior is unrestricted (and therefore is platform-dependent under your "finite number of states" reasoning).
|
73

Maybe simpler wording could be easier to understand than the rigorous definition of the standards.

implementation-defined behavior:
The language says that we have data-types. The compiler vendors specify what sizes shall they use, and provide a documentation of what they did.

undefined behavior:
You are doing something wrong. For example, you have a very large value in an int that doesn't fit in char. How do you put that value in char? actually there is no way! Anything could happen, but the most sensible thing would be to take the first byte of that int and put it in char. It is just wrong to do that to assign the first byte, but thats what happens under the hood.

unspecified behavior:
Which of these two functions is executed first?

void fun(int n, int m);

int fun1() {
    std::cout << "fun1";
    return 1;
}
int fun2() {
    std::cout << "fun2";
    return 2;
}

//...

fun(fun1(), fun2()); // which one is executed first?

The language doesn't specify the evaluation, left to right or right to left! So an unspecified behavior may or mayn't result in an undefined behavior, but certainly your program should not produce an unspecified behavior.


@eSKay I think your question is worth editing the answer to clarify more :)

for fun(fun1(), fun2()); isn't the behaviour "implementation defined"? The compiler has to choose one or the other course, after all?

The difference between implementation-defined and unspecified, is that the compiler is supposed to pick a behavior in the first case but it doesn't have to in the second case. For example, an implementation must have one and only one definition of sizeof(int). So, it can't say that sizeof(int) is 4 for some portion of the program and 8 for others. Unlike unspecified behavior, where the compiler can say: "OK I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left." It can happen in the same program, that's why it is called unspecified. In fact, C++ could have been made easier if some of the unspecified behaviors were specified. Take a look here at Dr. Stroustrup's answer for that:

It is claimed that the difference between what can be produced giving the compiler this freedom and requiring "ordinary left-to-right evaluation" can be significant. I'm unconvinced, but with innumerable compilers "out there" taking advantage of the freedom and some people passionately defending that freedom, a change would be difficult and could take decades to penetrate to the distant corners of the C and C++ worlds. I am disappointed that not all compilers warn against code such as ++i+i++. Similarly, the order of evaluation of arguments is unspecified.

IMO far too many "things" are left undefined, unspecified, that's easy to say and even to give examples of, but hard to fix. It should also be noted that it is not all that difficult to avoid most of the problems and produce portable code.

14 Comments

for fun(fun1(), fun2()); isn't the behaviour "implementation defined"? The compiler has to choose one or the other course, after all?
@AraK: thanks for the explaining. I understand it now. Btw, "I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left" I understand this can happen. Does it really, with compilers that we use these days?
@eSKay You have to ask a guru about this who got his hands dirty with many compilers :) AFAIK VC evaluates arguments right-to-left always.
@Lazer: It can definitely happen. Simple scenario: foo(bar, boz()) and foo(boz(), bar), where bar is an int and boz() is a function returning int. Assume a CPU where parameters are expected to be passed in registers R0-R1. Function results are returned in R0; functions may trash R1. Evaluating "bar" before "boz()" would require saving a copy of bar somewhere else before calling boz() and then loading that saved copy. Evaluating "bar" after "boz()" will avoid a memory store and re-fetch, and is an optimization many compilers would do regardless of their order in the argument list.
I don't know about C++ but the C standard says that a conversion of an int to a char is either implementation defined or even well defined (depending on the actual values and signedness of types). See C99 §6.3.1.3 (unchanged in C11).
|
34

From the official C Rationale Document

The terms unspecified behavior, undefined behavior, and implementation-defined behavior are used to categorize the result of writing programs whose properties the Standard does not, or cannot, completely describe. The goal of adopting this categorization is to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard. Appendix F to the Standard catalogs those behaviors which fall into one of these three categories.

Unspecified behavior gives the implementor some latitude in translating programs. This latitude does not extend as far as failing to translate the program.

Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior.

Implementation-defined behavior gives an implementor the freedom to choose the appropriate approach, but requires that this choice be explained to the user. Behaviors designated as implementation-defined are generally those in which a user could make meaningful coding decisions based on the implementation definition. Implementors should bear in mind this criterion when deciding how extensive an implementation definition ought to be. As with unspecified behavior, simply failing to translate the source containing the implementation-defined behavior is not an adequate response.

2 Comments

Hyper-modern compiler writers also regard "undefined behavior" as giving compiler writers license to assume that programs will never receive inputs that would cause Undefined Behavior, and to arbitrarily change all aspects of how the programs behave when they receive such inputs.
Another point I just noticed: C89 did not use the term "extension" to describe features that were guaranteed on some implementations but not others. The authors of C89 recognized that the majority of then-current implementations would treat signed arithmetic and unsigned arithmetic identically except when the results were used in certain ways, and such treatment applied even in case of signed overflow; they did not list that as a common extention in Annex J2, however, which suggests to me they viewed it as a natural state of affairs, rather than an extension.
16

Undefined Behavior vs. Unspecified Behavior has a short description of it.

Their final summary:

To sum up, unspecified behavior is usually something you shouldn't worry about, unless your software is required to be portable. Conversely, undefined behavior is always undesirable and should never occur.

1 Comment

There are two kinds of compilers: those which, unless explicitly documented otherwise, interpret most of the Standard's forms of Undefined Behavior as falling back upon characteristic behaviors documented by the underlying environment, and those which by default only usefully expose behaviors which the Standard characterizes as Implementation-Defined. When using compilers of the first type, many things of the first type can be done efficiently and safely using UB. Compilers for the second type will only be suitable for such tasks if they provide options to guarantee behavior in such cases.
13

Implementation defined-

Implementors wish,should be well documented,standard gives choices but sure to compile

Unspecified -

Same as implementation-defined but not documented

Undefined-

Anything might happen,take care of it.

1 Comment

I think it's important to note that the practical meaning of "undefined" has changed over the last few years. It used to be that given uint32_t s;, evaluating 1u<<s when s is 33 could be expected to maybe yield 0 or maybe yield 2, but not do anything else wacky. Newer compilers, however, evaluating 1u<<s may cause a compiler to determine that because s must have been less than 32 beforehand, any code before or after that expression which would only be relevant if s had been 32 or greater may be omitted.
11

Historically, both Implementation-Defined Behavior and Undefined Behavior represented situations in which the authors of the Standard expected that people writing quality implementations would use judgment to decide what behavioral guarantees, if any, would be useful for programs in the intended application field running on the intended targets. The needs of high-end number-crunching code are quite different from those of low-level systems code, and both UB and IDB give compiler writers flexibility to meet those different needs. Neither category mandates that implementations behave in a way that's useful for any particular purpose, or even for any purpose whatsoever. Quality implementations that claim to be suitable for a particular purpose, however, should behave in a manner befitting such purpose whether the Standard requires it or not.

The only difference between Implementation-Defined Behavior and Undefined Behavior is that the former requires that implementations define and document a consistent behavior even in cases where nothing the implementation could possibly do would be useful. The dividing line between them is not whether it would generally be useful for implementations to define behaviors (compiler writers should define useful behaviors when practical whether the Standard requires them to or not) but whether there might be implementations where defining a behavior would be simultaneously costly and useless. A judgment that such implementations might exist does not in any way, shape, or form, imply any judgment about the usefulness of supporting a defined behavior on other platforms.

Unfortunately, since the mid 1990s compiler writers have started to interpret the lack of behavioral mandates as an judgment that behavioral guarantees aren't worth the cost even in application fields where they're vital, and even on systems where they cost practically nothing. Instead of treating UB as an invitation to exercise reasonable judgment, compiler writers have started treating it as an excuse not to do so.

For example, given the following code:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

a two's-complement implementation would not have to expend any effort whatsoever to treat the expression v << pow as a two's-complement shift without regard for whether v was positive or negative.

The preferred philosophy among some of today's compiler writers, however, would suggest that because v can only be negative if the program is going to engage in Undefined Behavior, there's no reason to have the program clip the negative range of v. Even though left-shifting of negative values used to be supported on every single compiler of significance, and a large amount of existing code relies upon that behavior, modern philosophy would interpret the fact that the Standard says that left-shifting negative values is UB as implying that compiler writers should feel free to ignore that.

9 Comments

But handling undefined behavior in a nice way doesn't come for free. The whole reason that modern compilers exhibit such bizarre behavior in some cases of UB is that they are relentlessly optimizing, and to do the best job at that, they have to be able to assume that UB never occurs.
But the fact that << is UB on negative numbers is a nasty little trap and I'm glad to be reminded of that!
@TomSwirly: Unfortunately, compiler writers don't care that offering loose behavioral guarantees beyond those mandated by the Standard can often allow a massive speed boost compared with requiring that code avoid at all costs anything not defined by the Standard. If a programmer doesn't care whether i+j>k yields 1 or 0 in cases where the addition overflows, provided it has no other side effects, a compiler may be able to make some massive optimizations that would not be possible if the programmer wrote the code as (int)((unsigned)i+j) > k.
@TomSwirly: To them, if compiler X can take a strictly-conforming program to do some task T and yield an executable that is 5% more efficient than compiler Y would yield with that same program, that means X is better, even if Y could generate code that did the same task three times as efficiently given a program that exploits behaviors that Y guarantees but X does not.
@PSkocik: Consider as a simple scenario a situation where a i, j, and k are arguments to a function a compiler is expanding in line for a function call foo(x, y, x). In that scenario, a compiler could replace i+j > k with x+y > x, which it could in turn replaced with y > 0, skipping the addition entirely, eliminating any dependency on the value of x, and possibly allowing a compiler to eliminate the comparison and any dependency upon the exact value of y if it can determine that y will always be positive.
|
6

C++ standard n3337 § 1.3.10 implementation-defined behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

Sometimes C++ Standard doesn't impose particular behavior on some constructs but says instead that a particular, well defined behavior has to be chosen and described by particular implementation (version of library). So user can still know exactly how will program behave even though Standard doesn't describe this.


C++ standard n3337 § 1.3.24 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 ]

When the program encounters construct that is not defined according to C++ Standard it is allowed to do whatever it wants to do ( maybe send an email to me or maybe send an email to you or maybe ignore the code completely).


C++ standard n3337 § 1.3.25 unspecified behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation [ Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this International Standard. — end note ]

C++ Standard doesn't impose particular behavior on some constructs but says instead that a particular, well-defined behavior has to be chosen ( but not necessarily described) by particular implementation (version of library). So in the case when no description has been provided it can be difficult for the user to know exactly how the program will behave.

Comments

3

Update 2024:

For C++ Since C++26 we also have erroneous behavior which is different than undefined-behavior.

This post discusses it in length: What is erroneous behavior? How is it different from undefined behavior?.

However since the current post is the main one regarding all sorts of "problematic" behviors,
I thought it would be helpful to post a summary here for completeness:

  • The main motivation is to reduce the volunarablity to security attacks due to fact that UB allows the compiler to do almost anything.
  • This feature is a part of the effort of the C++ committee to direct the language to a safer place.
  • Erroneous behavior starts with an erroneous value, e.g. as a result of an uninitialized variable.
  • Erroneous behavior comes with the cost of loosing some optimization opportunities.
  • An uninitialized variable can keep the pre-C++26 behavior by using the [[indeterminate]] attribute.

The description from cppreference:

erroneous behavior - (incorrect) behavior that the implementation is recommended to diagnose.

  • Erroneous behavior is always the consequence of incorrect program code.
  • The evaluation of a constant expression never results in an erroneous behavior.
  • If the execution contains an operation specified as having erroneous behavior, the implementation is permitted and recommended to issue a diagnostic, and is permitted to terminate the execution at an unspecified time after that operation.
  • An implementation can issue a diagnostic if it can determine that erroneous behavior is reachable under an implementation-specific set of assumptions about the program behavior, which can result in false positives.

Finally this post might be of intereset: In C++26, are implementations required to "initialize" uninitialized variables to some fixed byte pattern?.
According to it the compiler should indeed ensure some kind of initialization.
Since this has a performance effect, you can use the [[indeterminate]] attribute as mentioned above to avoid it.

5 Comments

Regarding the downvote: the button tooltip is "This answer is not useful". How is it not useful to have some info about the new erroneous behavior in the main post dealing with all kind of "problematic" behaviors ?
A fundamental problem with UB is that the C and C++ Standards allow implementations which are intended solely for use with correct portable programs that will never receive erroneous data to assume that corner cases will never occur, but implementations like clang and gcc fail to clearly state in their documentation that their optimizations are specialized for that particular use case and are not intended to be suitable for others. Having actions which may either behave without side effects or trap in asynchronous but defined fashion is a major improvement, but to really fix things...
...would require recognizing more scenarios where compilers are allowed to perform certain optimizing transforms even if their effects might be observable, as was done with consolidated allocations using custom allocators (if two objects' lifetimes will always begin and end simultnaneously, an implementation may use a single allocation at the start of their lifetime to store them both, if it then releases that single allocation when their lifetime ends, even though this might be observable on an allocator that e.g. produces a log of all allocations)
@supercat I guess what you wrote makes some sense. I myself do not feel I can review the committee's decisions in any way. The purpose of my answer was just to complete this post dealing with all kinds of "problematic" behaviors with the latest news for C++26.
The problem isn't with the Committee's decisions, but rather with how the authors of clang and gcc misapply them. The C Standard recognizes three ways UB can occur; an assumption that UB is impossible would require an assumption that all three of those ways were impossible. For many compiler use cases, at least one of those assumptions would be patently absurd. Recognizing erroneous behavior may be a step in the right direction, but doesn't address the fundamentally broken assumptions.
2

Undefined behavior is ugly — as in, "The good, the bad, and the ugly".

Good: a program that compiles and works, for the right reasons.

Bad: a program that has an error, of a kind that the compiler can detect and complain about.

Ugly: a program that has an error, that the compiler cannot necessarily detect and warn about, meaning that the program compiles, and may seem to work correctly some of the time, but also fails bizarrely some of the time. That's what undefined behavior is.

Some programming languages and other formal systems try hard to limit the "gulf of undefinedness" — that is, they try to arrange things so that most or all programs are either "good" or "bad", and that very few are "ugly". It's a characteristic of C, however, that its "gulf of undefinedness" is relatively wide.

Undefined behavior is also tricky because it can arise for several reasons and manifest itself in several ways. There's undefined behavior that's simply impossible for the compiler to detect (because doing so would be equivalent to the Halting problem). There's undefined behavior that compilers didn't traditionally warn about, although given effort (and perhaps a non-default invocation flag like gcc's -Wall), today plenty of compilers do. Finally, there's undefined behavior that becomes well-defined (in certain environments) because a particular implementation defines it.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.