Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 121: Unspecified behavior

Section 121.1: Value of an out-of-range enum

If a scoped enum is converted to an integral type that is too small to hold its value, the resulting value is unspecified. Example:

enum class E { X = 1,

Y = 1000,

};

// assume 1000 does not fit into a char

char c1 = static_cast<char>(E::X); // c1 is 1

char c2 = static_cast<char>(E::Y); // c2 has an unspecified value

Also, if an integer is converted to an enum and the integer's value is outside the range of the enum's values, the resulting value is unspecified. Example:

enum Color { RED = 1, GREEN = 2, BLUE = 3,

};

Color c = static_cast<Color>(4);

However, in the next example, the behavior is not unspecified, since the source value is within the range of the enum, although it is unequal to all enumerators:

enum Scale { ONE = 1, TWO = 2, FOUR = 4,

};

Scale s = static_cast<Scale>(3);

Here s will have the value 3, and be unequal to ONE, TWO, and FOUR.

Section 121.2: Evaluation order of function arguments

If a function has multiple arguments, it is unspecified what order they are evaluated in. The following code could print x = 1, y = 2 or x = 2, y = 1 but it is unspecified which.

int f(int x, int y) {

printf("x = %d, y = %d\n", x, y);

}

int get_val() { static int x = 0; return ++x;

}

int main() {

f(get_val(), get_val());

}

Version ≥ C++17

In C++17, the order of evaluation of function arguments remains unspecified.

GoalKicker.com – C++ Notes for Professionals

595

However, each function argument is completely evaluated, and the calling object is guaranteed evaluated before any function arguments are.

struct from_int {

from_int(int x) { std::cout << "from_int (" << x << ")\n"; }

};

int make_int(int x){ std::cout << "make_int (" << x << ")\n"; return x; }

void foo(from_int a, from_int b) {

}

void bar(from_int a, from_int b) {

}

auto which_func(bool b){

std::cout << b?"foo":"bar" << "\n"; return b?foo:bar;

}

int main(int argc, char const*const* argv) { which_func( true )( make_int(1), make_int(2) );

}

this must print:

bar make_int(1) from_int(1) make_int(2) from_int(2)

or

bar make_int(2) from_int(2) make_int(1) from_int(1)

it may not print bar after any of the make or from's, and it may not print:

bar make_int(2) make_int(1) from_int(2) from_int(1)

or similar. Prior to C++17 printing bar after make_ints was legal, as was doing both make_ints prior to doing any from_ints.

Section 121.3: Result of some reinterpret_cast conversions

The result of a reinterpret_cast from one function pointer type to another, or one function reference type to another, is unspecified. Example:

int f();

GoalKicker.com – C++ Notes for Professionals

596

auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value

Version ≤ C++03

The result of a reinterpret_cast from one object pointer type to another, or one object reference type to another, is unspecified. Example:

int x = 42;

char* p = reinterpret_cast<char*>(&x); // p has unspecified value

However, with most compilers, this was equivalent to static_cast<char*>(static_cast<void*>(&x)) so the resulting pointer p pointed to the first byte of x. This was made the standard behavior in C++11. See type punning conversion for more details.

Section 121.4: Space occupied by a reference

A reference is not an object, and unlike an object, it is not guaranteed to occupy some contiguous bytes of memory. The standard leaves it unspecified whether a reference requires any storage at all. A number of features of the language conspire to make it impossible to portably examine any storage the reference might occupy:

If sizeof is applied to a reference, it returns the size of the referenced type, thereby giving no information about whether the reference occupies any storage.

Arrays of references are illegal, so it is not possible to examine the addresses of two consecutive elements of a hypothetical reference of arrays in order to determine the size of a reference.

If the address of a reference is taken, the result is the address of the referent, so we cannot get a pointer to the reference itself.

If a class has a reference member, attempting to extract the address of that member using offsetof yields undefined behavior since such a class is not a standard-layout class.

If a class has a reference member, the class is no longer standard layout, so attempts to access any data used to store the reference results in undefined or unspecified behavior.

In practice, in some cases a reference variable may be implemented similarly to a pointer variable and hence occupy the same amount of storage as a pointer, while in other cases a reference may occupy no space at all since it can be optimized out. For example, in:

void f() { int x;

int& r = x;

// do something with r

}

the compiler is free to simply treat r as an alias for x and replace all occurrences of r in the rest of the function f with x, and not allocate any storage to hold r.

Section 121.5: Moved-from state of most standard library classes

Version ≥ C++11

All standard library containers are left in a valid but unspecified state after being moved from. For example, in the following code, v2 will contain {1, 2, 3, 4} after the move, but v1 is not guaranteed to be empty.

int main() {

std::vector<int> v1{1, 2, 3, 4}; std::vector<int> v2 = std::move(v1);

GoalKicker.com – C++ Notes for Professionals

597

}

Some classes do have a precisely defined moved-from state. The most important case is that of std::unique_ptr<T>, which is guaranteed to be null after being moved from.

Section 121.6: Result of some pointer comparisons

If two pointers are compared using <, >, <=, or >=, the result is unspecified in the following cases:

The pointers point into di erent arrays. (A non-array object is considered an array of size 1.)

int x;

 

 

int y;

 

 

const bool b1 = &x < &y;

// unspecified

int a[10];

 

 

const bool b2 = &a[0] < &a[1];

//

true

const bool b3 = &a[0] < &x;

//

unspecified

const bool b4 = (a + 9) < (a + 10); //

true

 

//

note: a+10 points past the end of the array

 

 

 

The pointers point into the same object, but to members with di erent access control.

class A { public:

int x; int y;

bool f1() { return &x < &y; } // true; x comes before y bool f2() { return &x < &z; } // unspecified

private: int z;

};

Section 121.7: Static cast from bogus void* value

If a void* value is converted to a pointer to object type, T*, but is not properly aligned for T, the resulting pointer value is unspecified. Example:

//Suppose that alignof(int) is 4 int x = 42;

void* p1 = &x;

//Do some pointer arithmetic...

void* p2 = static_cast<char*>(p1) + 2; int* p3 = static_cast<int*>(p2);

The value of p3 is unspecified because p2 cannot point to an object of type int; its value is not a properly aligned address.

Section 121.8: Order of initialization of globals across TU

Whereas inside a Translation Unit, order of initialization of global variables is specified, order of initialization across Translation Units is unspecified.

So program with following files

GoalKicker.com – C++ Notes for Professionals

598

foo.cpp

#include <iostream>

int dummyFoo = ((std::cout << "foo"), 0);

bar.cpp

#include <iostream>

int dummyBar = ((std::cout << "bar"), 0);

main.cpp

int main() {}

might produce as output:

foobar

or

barfoo

That may lead to Static Initialization Order Fiasco.

GoalKicker.com – C++ Notes for Professionals

599