Undefined behavior

Uses your system voice; voice and rate in settings.

Snapshot

≈ 10 min read

Names in other languages

No names in other languages are held for this entry yet.

Article bodies are English and are not machine-translated. A name in another language is not a translation of the article.

A computer program exhibits undefined behavior (UB) when it contains, or is executing code for which its programming language specification does not mandate any specific requirements. This is different from unspecified behavior, for which the language specification does not prescribe a result, and implementation-defined behavior that defers to the documentation of another component of the platform (such as the ABI or the translator documentation). In the C programming community, undefined behavior may be humorously referred to as "nasal demons", after a comp.std.c post that explained undefined behavior as allowing the compiler to do anything it chooses, even "to make demons fly out of your nose".

A computer program exhibits undefined behavior (UB) when it contains, or is executing code for which its programming language specification does not mandate any specific requirements. This is different from unspecified behavior, for which the language specification does not prescribe a result, and implementation-defined behavior that defers to the documentation of another component of the platform (such as the ABI or the translator documentation). In the C programming community, undefined behavior may be humorously referred to as "nasal demons", after a comp.std.c post that explained undefined behavior as allowing the compiler to do anything it chooses, even "to make demons fly out of your nose". For C and C++, the compiler is allowed to give a compile-time diagnostic in these cases, but is not required to: the implementation will be considered correct whatever it does in such cases, analogous to don't-care terms in digital logic. It is the responsibility of the programmer to write code that never invokes undefined behavior, although compiler implementations are allowed to issue diagnostics when this happens. Compilers nowadays have flags that enable such diagnostics, for example, -fsanitize=undefined enables the "undefined behavior sanitizer" (UBSan) in gcc 4.9 and in clang. However, this flag is not the default and enabling it is a choice of the person who builds the code. Under some circumstances there can be specific restrictions on undefined behavior. For example, the instruction set specifications of a CPU might leave the behavior of some forms of an instruction undefined, but if the CPU supports memory protection then the specification will probably include a blanket rule stating that no user-accessible instruction may cause a hole in the operating system's security; so an actual CPU would be permitted to corrupt user registers in response to such an instruction, but would not be allowed to, for example, switch into supervisor mode. The runtime platform can also provide some restrictions or guarantees on undefined behavior, if the toolchain or the runtime explicitly document that specific constructs found in the source code are mapped to specific well-defined mechanisms available at runtime. For example, an interpreter may document a particular behavior for some operations that are undefined in the language specification, while other interpreters or compilers for the same language may not. A compiler produces executable code for a specific ABI, filling the semantic gap in ways that depend on the compiler version: the documentation for that compiler version and the ABI specification can provide restrictions on undefined behavior. Relying on these implementation details makes the software non-portable, but portability may not be a concern if the software is not supposed to be used outside of a specific runtime. Undefined behavior can result in a program crash or even in failures that are harder to detect and make the program look like it is working normally, such as silent loss of data and production of incorrect results. ===Erroneous program=== In the design of programming languages, an erroneous program is one whose semantics are not well-defined, but where the language implementation is not obligated to signal an error either at compile or at execution time. For example, in Ada: In addition to bounded errors, the language rules define certain kinds of errors as leading to erroneous execution. Like bounded errors, the implementation need not detect such errors either prior to or during run time. Unlike bounded errors, there is no language-specified bound on the possible effect of erroneous execution; the effect is in general not predictable. Defining a condition as "erroneous" means that the language implementation need not perform a potentially expensive check (e.g. that a global variable refers to the same object as a subroutine parameter) but may nonetheless depend on a condition being true in defining the semantics of the program. int foo(unsigned char x) { int value = 2147483600; // assuming 32-bit int and 8-bit char value += x; if (value The value of x cannot be negative and, given that signed integer overflow is undefined behavior in C, the compiler can assume that value will always be false. Thus the if statement, including the call to the function bar , can be ignored by the compiler since the test expression in the if has no side effects and its condition will never be satisfied. The code is therefore semantically equivalent to: int foo(unsigned char x) { int value = 2147483600; value += x; return value; } Had the compiler been forced to assume that signed integer overflow has wraparound behavior, then the transformation above would not have been legal. Such optimizations become hard to spot by humans when the code is more complex and other optimizations, like inlining, take place. For example, another function may call the above function: void run_tasks(unsigned char* ptrx) { int z; z = foo(*ptrx); while (*ptrx > 60) { run_one_task(ptrx, z); } } The compiler is free to optimize away the while -loop here by applying value range analysis: by inspecting foo() , it knows that the initial value pointed to by ptrx cannot possibly exceed 47 (as any more would trigger undefined behavior in foo() ); therefore, the initial check of *ptrx > 60 will always be false in a conforming program. Going further, since the result z is now never used and foo() has no side effects, the compiler can optimize run_tasks() to be an empty function that returns immediately. The disappearance of the while -loop may be especially surprising if foo() is defined in a separately compiled object file. Another benefit from allowing signed integer overflow to be undefined is that it makes it possible to store and manipulate a variable's value in a processor register that is larger than the size of the variable in the source code. For example, if the type of a variable as specified in the source code is narrower than the native register width (such as int on a 64-bit machine, a common scenario), then the compiler can safely use a signed 64-bit integer for the variable in the machine code it produces, without changing the defined behavior of the code. If a program depended on the behavior of a 32-bit integer overflow, then a compiler would have to insert additional logic when compiling for a 64-bit machine, because the overflow behavior of most machine instructions depends on the register width. Undefined behavior also allows more compile-time checks by both compilers and static program analysis. char* p = "wikipedia"; // valid C, deprecated in C++98/C++03, ill-formed as of C++11 p[0] = 'W'; // undefined behavior Integer division by zero results in undefined behavior: int x = 1; return x / 0; // undefined behavior Certain pointer operations may result in undefined behavior: int arr[4] = {0, 1, 2, 3}; int* p = arr + 5; // undefined behavior for indexing out of bounds p = nullptr; int a = *p; // undefined behavior for dereferencing a null pointer In C and C++, the relational comparison of pointers to objects (for less-than or greater-than comparison) is only strictly defined if the pointers point to members of the same object, or elements of the same array. Example: int main(void) { int a = 0; int b = 0; return &a Reaching the end of a value-returning function (other than main() ) without a return statement results in undefined behavior if the value of the function call is used by the caller: int f() {} int x = f(); // undefined behavior Modifying an object between two sequence points more than once produces undefined behavior. There are considerable changes in what causes undefined behavior in relation to sequence points as of C++11. Modern compilers can emit warnings when they encounter multiple unsequenced modifications to the same object. The following example will cause undefined behavior in both C and C++. int f(int i) { // undefined behavior: two unsequenced modifications to i return i++ + i++; } When modifying an object between two sequence points, reading the value of the object for any other purpose than determining the value to be stored is also undefined behavior. a[i] = i++; // undefined behavior printf("%d %d\n", ++n, pow(2, n)); // also undefined behavior In C/C++ bitwise shifting a value by a number of bits which is either a negative number or is greater than or equal to the total number of bits in this value results in undefined behavior. The safest way (regardless of compiler vendor) is to always keep the number of bits to shift (the right operand of the and >> bitwise operators) within the range: [ , ] (where value is the left operand). int num = -1; unsigned int val = 1 using System; unsafe { int* p = (int*)0x12345678; Console.WriteLine(*p); // reading an arbitrary memory address } A use-after-free of stack memory also triggers undefined behavior. using System; unsafe int* GetPointer() { int x = 100; return &x; } int* p = GetPointer(); Console.WriteLine(*p); // gets a pointer to no-longer valid stack memory int x = 0; boolean ready = false; Thread t1 = new Thread(() -> { x = 33; ready = true; }); Thread t2 = new Thread(() -> { if (ready) { System.out.println(x); // may print 33 or 0 } }); t1.start(); t2.start(); Undefined memory can arise in Java Native Interface calls that may case undefined behavior. From C: include JNIEXPORT jint JNICALL Java_Crash_boom(JNIEnv* env, jclass cls) { int* p = NULL; return *p; // dereferencing a null pointer } On Java: package org.wikipedia.examples; public class Crash { static { System.loadLibrary("crash"); } private static native int boom(); public static void main(String[] args) { System.out.println(boom()); } } use std::mem; /// Constructs a null reference. pub const fn null_ref () -> &T { unsafe { mem::zeroed() } } Calling null_ref is always malformed due to the invariants imposed by all reference types, even though the function itself is not an unsafe fn item and can be called from safe code. Furthermore, dereferencing any null pointer is undefined, although many host systems are still designed to handle such cases in a segmentation fault: use std::ptr; fn main() { let p: *const i32 = ptr::null(); // SAFETY: `p` is null and may not be dereferenced. unsafe { *p }; }

Text derived from https://en.wikipedia.org/w/index.php?oldid=1375331775 under CC BY-SA 4.0; modifications have been made.