Renders the entire program meaningless if certain rules of the language are violated.
[edit] ExplanationThe C++ standard precisely defines the observable behavior of every C++ program that does not fall into one of the following classes:
#include <cassert> #include <cstring> void f() { int d1, d2; // d1, d2 have erroneous values int e1 = d1; // erroneous behavior int e2 = d1; // erroneous behavior assert(e1 == e2); // holds assert(e1 == d1); // holds, erroneous behavior assert(e2 == d1); // holds, erroneous behavior std::memcpy(&d2, &d1, sizeof(int)); // no erroneous behavior, but // d2 has an erroneous value assert(e1 == d2); // holds, erroneous behavior assert(e2 == d2); // holds, erroneous behavior } unsigned char g(bool b) { unsigned char c; // c has erroneous value unsigned char d = c; // no erroneous behavior, but d has an erroneous value assert(c == d); // holds, both integral promotions have erroneous behavior int e = d; // erroneous behavior return b ? d : 0; // erroneous behavior if b is true }(since C++26)
Because correct C++ programs are free of undefined behavior, compilers may produce unexpected results when a program that actually has UB is compiled with optimization enabled:
For example,
[edit] Signed overflowint foo(int x) { return x + 1 > x; // either true or UB due to signed overflow }
may be compiled as (demo)
[edit] Access out of boundsint table[4] = {}; bool exists_in_table(int v) { // return true in one of the first 4 iterations or UB due to out-of-bounds access for (int i = 0; i <= 4; i++) if (table[i] == v) return true; return false; }
May be compiled as (demo)
exists_in_table(int): mov eax, 1 ret[edit] Uninitialized scalar
May be compiled as (demo)
The output shown was observed on an older version of gcc
#include <cstdio> int main() { bool p; // uninitialized local variable if (p) // UB access to uninitialized scalar std::puts("p is true"); if (!p) // UB access to uninitialized scalar std::puts("p is false"); }
Possible output:
[edit] Invalid scalarint f() { bool b = true; unsigned char* p = reinterpret_cast<unsigned char*>(&b); *p = 10; // reading from b is now UB return b == 0; }
May be compiled as (demo)
[edit] Null pointer dereferenceThe examples demonstrate reading from the result of dereferencing a null pointer.
int foo(int* p) { int x = *p; if (!p) return x; // Either UB above or this branch is never taken else return 0; } int bar() { int* p = nullptr; return *p; // Unconditional UB }
may be compiled as (demo)
foo(int*): xor eax, eax ret bar(): ret[edit] Access to pointer passed to std::realloc
Choose clang to observe the output shown
#include <cstdlib> #include <iostream> int main() { int* p = (int*)std::malloc(sizeof(int)); int* q = (int*)std::realloc(p, sizeof(int)); *p = 1; // UB access to a pointer that was passed to realloc *q = 2; if (p == q) // UB access to a pointer that was passed to realloc std::cout << *p << *q << '\n'; }
Possible output:
[edit] Infinite loop without side-effectsChoose clang or the latest gcc to observe the output shown.
#include <iostream> bool fermat() { const int max_value = 1000; // Non-trivial infinite loop with no side effects is UB for (int a = 1, b = 1, c = 1; true; ) { if (((a * a * a) == ((b * b * b) + (c * c * c)))) return true; // disproved :() a++; if (a > max_value) { a = 1; b++; } if (b > max_value) { b = 1; c++; } if (c > max_value) c = 1; } return false; // not disproved } int main() { std::cout << "Fermat's Last Theorem "; fermat() ? std::cout << "has been disproved!\n" : std::cout << "has not been disproved.\n"; }
Possible output:
Fermat's Last Theorem has been disproved![edit] Ill-formed with diagnostic message
Note that compilers are permitted to extend the language in ways that give meaning to ill-formed programs. The only thing C++ standard requires in such cases is a diagnostic message (compiler warning), unless the program was "ill-formed no diagnostic required".
For example, unless language extensions are disabled via --pedantic-errors
, GCC will compile the following example with only a warning even though it appears in the C++ standard as an example of an "error" (see also GCC Bugzilla #55783)
#include <iostream> // Example tweak, do not use constant double a{1.0}; // C++23 standard, §9.4.5 List-initialization [dcl.init.list], Example #6: struct S { // no initializer-list constructors S(int, double, double); // #1 S(); // #2 // ... }; S s1 = {1, 2, 3.0}; // OK, invoke #1 S s2{a, 2, 3}; // error: narrowing S s3{}; // OK, invoke #2 // â end example] S::S(int, double, double) {} S::S() {} int main() { std::cout << "All checks have passed.\n"; }
Possible output:
main.cpp:17:6: error: type 'double' cannot be narrowed to 'int' in initializer â® list [-Wc++11-narrowing] S s2{a, 2, 3}; // error: narrowing ^ main.cpp:17:6: note: insert an explicit cast to silence this issue S s2{a, 2, 3}; // error: narrowing ^ static_cast<int>( ) 1 error generated.[edit] References Extended content
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4