Show Lecture.NotFullySpecified as a slide show.
The C++ standard defines several kinds of not-fully specified things:
Implementation-defined (§1.9.2):
Unspecified behavior (§1.9.3):
Undefined behavior (§1.9.4):
A choice made by the compiler, which must be documented.
// Size of variables: cout << sizeof(int) << '\n';
4
// Maximum value of a double: double d = 6e307; cout << d << '\n' << d*2 << '\n' << d*3;
6e+307 1.2e+308 inf
Such choices are often heavily influenced by the hardware.
// Signed overflow: short s = 32767; cout << ++s;
-32768
// Character set: switch ('$') { case 0x24: cout << "ASCII or UTF-8\n"; break; case 0x5b: cout << "EBCDIC\n"; break; default: cout << "WTF!?\n"; break; }
ASCII or UTF-8
// The result of shifting a negative signed value right: cout << (-1 >> 4) << '\n';
-1
// The result of system(): system("date");
Sun Apr 28 16:16:12 MDT 2024
A choice made by the compiler, need not be documented or consistent, generally a “this-or-that” sort of choice.
// Order of evaluation of an expression (mostly): int foo() { cout << "foo"; return 0; } int bar() { cout << "bar"; return 0; } int main() { return foo()*bar(); }
foobar
// Comparing addresses of different objects: int a,b; cout << boolalpha << (&a < &b);
false
// Order of evaluation of function arguments: int foo() { cout << "foo"; return 0; } int bar() { cout << "bar"; return 0; } void ignore_arguments(int, int) { } int main() { ignore_arguments(foo(), bar()); }
barfoo
I suspect that byte order (little-endian, big-endian) is unspecified, since a program can’t detect byte order without an unspecified operation:
int word = 0x12345678; short *usp = reinterpret_cast<short *>(&word); cout << hex << *usp << '\n';
5678
With undefined behavior, all bets are off! Anything can happen. Consistency is not required. Warnings are not required.
// Uninitialized & out-of-range values: int a[135]; a[30] = 0; for (int i=1; ; i*=2) cout << i << ": " << a[i] << endl;
1: 0 2: 0 4: 0 8: 0 16: 0 32: 0 64: 0 128: 4196656 256: 1382438695 512: 0 1024: 0 2048: 0 SIGSEGV: Segmentation fault
// Dereferencing a null pointer: cout << "This will certainly be displayed!\n"; int *p = nullptr; cout << *p << '\n';
SIGSEGV: Segmentation fault
// Shifting too far: int amount=35; cout << (1<<amount);
8
// Multiple writes to the same location // in a single expression: int a = 0; cout << ++a + ++a << '\n';
c.cc:4: warning: operation on 'a' may be undefined 4
int b; cout << b << '\n';
c.cc:2: warning: 'b' is used uninitialized in this function 0
g++ notices some undefined behavior, not all. This is a QOI (Quality Of Implementation) aspect, but not a standards-conformance issue.
C++ is quite concerned about efficiency.
int
is implementation-defined so that the compiler
can use the natural size provided by the architecture.
double
is implementation-defined
so that the compiler can use the available hardware floating-point format.
C++’s attitude is “You break the rules, you pay the price.” It doesn’t hold your hand.
Information from the Unisys C Compiler Programming Reference Manual:
Type | Bits | sizeof | Signed Range | Unsigned Max |
---|---|---|---|---|
char | 9 | 1 | −255 to 255 | 511 |
short | 18 | 2 | −217+1 to 217−1 | 218−1 |
int | 36 | 4 | −235+1 to 235−1 | 236−2 |
long | 36 | 4 | −235+1 to 235−1 | 236−2 |
long long | 72 | 8 | −271+1 to 271−1 |