C++
Неопределенное поведение
Поиск…
замечания
Если поведение конструкции не указано, то стандарт помещает некоторые ограничения на поведение, но оставляет некоторую свободу для реализации, что не требуется для документирования того, что происходит в данной ситуации. Это контрастирует с реализацией определяется поведением , в которых требуется осуществление документировать то , что происходит, и непредсказуемое поведение, в котором все может случиться.
Порядок инициализации globals через TU
Если внутри единицы перевода указывается порядок инициализации глобальных переменных, порядок инициализации между единицами перевода не указан.
Итак, программа со следующими файлами
foo.cpp
#include <iostream> int dummyFoo = ((std::cout << "foo"), 0);
bar.cpp
#include <iostream> int dummyBar = ((std::cout << "bar"), 0);
main.cpp
int main() {}
может вырабатываться как результат:
foobar
или же
barfoo
Это может привести к статическому порядку инициализации Fiasco .
Значение перераспределения вне диапазона
Если облачное перечисление преобразуется в интегральный тип, который слишком мал, чтобы удерживать его значение, результирующее значение не указывается. Пример:
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
Кроме того, если целое число преобразуется в перечисление, а значение целочисленного значения выходит за пределы значений перечисления, результирующее значение не указывается. Пример:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 3,
};
Color c = static_cast<Color>(4);
Однако в следующем примере поведение не является неопределенным, так как исходное значение находится в пределах диапазона перечисления, хотя оно неравномерно для всех счетчиков:
enum Scale {
ONE = 1,
TWO = 2,
FOUR = 4,
};
Scale s = static_cast<Scale>(3);
Здесь s
будет иметь значение 3 и быть неравным с ONE
, TWO
и FOUR
.
Статический литье из фиктивного значения void *
Если значение void*
преобразуется в указатель на тип объекта, T*
, но не правильно выровнено для T
, результирующее значение указателя не указывается. Пример:
// 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);
Значение p3
не определено, поскольку p2
не может указывать на объект типа int
; его значение не является правильно выровненным адресом.
Результат некоторых преобразований reinterpret_cast
Результат reinterpret_cast
из одного типа указателя функции в другой или один тип ссылочного типа в другой не указан. Пример:
int f();
auto fp = reinterpret_cast<int(*)(int)>(&f); // fp has unspecified value
Результат reinterpret_cast
от одного типа указателя объекта к другому, или один тип ссылки объекта к другому, не указан. Пример:
int x = 42;
char* p = reinterpret_cast<char*>(&x); // p has unspecified value
Однако с большинством компиляторов это было равнозначно static_cast<char*>(static_cast<void*>(&x))
поэтому полученный указатель p
указывал на первый байт x
. Это стало стандартным поведением в C ++ 11. Подробнее см. В разделе « Преобразование чисел» .
Результат некоторых сравнений указателей
Если два указателя сравниваются с использованием <
, >
, <=
, или >=
, результат не указывается в следующих случаях:
Указатели указывают на разные массивы. (Объект без массива считается массивом размера 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
Указатели указывают на один и тот же объект, но на участников с различным контролем доступа.
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; };
Пространство, занятое ссылкой
Ссылка не является объектом, и, в отличие от объекта, не гарантируется, что он занимает несколько смежных байтов памяти. Стандарт оставляет его неопределенным, если ссылка требует какого-либо хранения вообще. Ряд особенностей языкового сговора, чтобы сделать невозможным портативное исследование любого хранилища, которое может занимать эта ссылка:
- Если
sizeof
применяется к ссылке, он возвращает размер ссылочного типа, тем самым не давая информации о том, занимает ли эта ссылка какое-либо хранилище. - Массивы ссылок являются незаконными, поэтому невозможно определить адреса двух последовательных элементов гипотетической ссылки массивов, чтобы определить размер ссылки.
- Если адрес ссылки сделан, результатом будет адрес референта, поэтому мы не сможем получить указатель на саму ссылку.
- Если у класса есть ссылочный элемент, попытка извлечь адрес этого члена с помощью
offsetof
дает неопределенное поведение, поскольку такой класс не является стандартным классом макета. - Если класс имеет ссылочный элемент, класс больше не является стандартным макетом, поэтому попытки доступа к любым данным, используемым для хранения ссылочных результатов, в неопределенном или неуказанном поведении.
На практике в некоторых случаях эталонная переменная может быть реализована аналогично переменной указателя и, следовательно, занимает ту же сумму хранения, что и указатель, в то время как в других случаях эта ссылка может вообще не занимать места, поскольку ее можно оптимизировать. Например, в:
void f() {
int x;
int& r = x;
// do something with r
}
компилятор может просто рассматривать r
как псевдоним для x
и заменять все вхождения r
в остальную часть функции f
на x
и не выделять какое-либо хранилище для хранения r
.
Порядок оценки аргументов функции
Если функция имеет несколько аргументов, то неуказано, в какой порядок они оцениваются. Следующий код может печатать x = 1, y = 2
или x = 2, y = 1
но не указано, что.
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());
}
В C ++ 17 порядок оценки аргументов функции остается неопределенным.
Тем не менее, каждый аргумент функции полностью оценивается, и вызывающий объект гарантированно оценивается перед любыми аргументами функции.
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) );
}
это должно печатать:
bar
make_int(1)
from_int(1)
make_int(2)
from_int(2)
или же
bar
make_int(2)
from_int(2)
make_int(1)
from_int(1)
он не может печатать bar
после любого из make
или from
's, и он не может печатать:
bar
make_int(2)
make_int(1)
from_int(2)
from_int(1)
или похожие. До bar
C ++ 17 после make_int
s было законным, так как делало оба make_int
перед тем, как делать какие-либо from_int
s.
Перемещение из состояния большинства стандартных классов библиотеки
Все контейнеры стандартной библиотеки остаются в правильном, но неуказанном состоянии после перемещения из. Например, в следующем коде v2
будет содержать {1, 2, 3, 4}
после перемещения, но v1
не гарантированно будет пустым.
int main() {
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v2 = std::move(v1);
}
Некоторые классы имеют точно определенное перемещенное состояние. Самый важный случай - это std::unique_ptr<T>
, который после переноса будет иметь значение null.