Русская Википедия:Неуточнённое поведение
Шаблон:Не путать Неуточнённое поведение (Шаблон:Lang-en) и поведение, определяемое реализацией (Шаблон:Lang-en) — поведение компьютерной программы, которое может различаться на разных платформах и компиляторах, поскольку спецификация языка программирования предлагает несколько допустимых вариантов реализации некой языковой конструкции. В отличие от неопределённого поведения, программа с неуточнённым поведением с точки зрения соответствия спецификации языка не считается ошибочной; при неуточняемом поведении, спецификация обычно ограничивает возможные варианты поведения, хотя и не сводит их в единое допустимое.
Разница между тем и другим такая: поведение, определяемое реализацией, задокументированное и последовательное на данном процессоре, программном окружении, версии системы и т. д. Неуточнённое поведение может меняться от случая к случаю, но система обязательно сделает что-то разумное — а не уйдёт в аварийный режим.
Программист должен избегать:
- Неопределённого поведения — всегда. Пример: доступ по NULL-указателю недопустим.
- Неуточнённого поведения — там, где оно критично для результата программы. Пример: если две функции вызываются в неуточнённом порядке и в них общий отладочный код, это будет видно в отладочном журнале, но для результата может быть и не критично.
- Но: если реализация уточняет неопределённое или неуточнённое поведение, программист может на неё закладываться. Примеры: хоть в Си переполнение знакового типа — это неопределённое поведение, на большинстве современных архитектур 32767+1=−32768.
- Поведения, определяемого реализацией — если в числе поддерживаемых платформ есть такие, что ведут себя по-разному. Пример: большинство 8- и 16-битных платформ (в основном микроконтроллеры и старые компьютеры) говорят, что целый тип int — это два байта, но если поддерживаем только сравнительно мощные машины, можно считать, что int — четыре байта.
Терминология
Согласно стандарту языка C99,
- 3.4.1. поведение, определяемое реализацией (Шаблон:Lang-en) — неуточняемое поведение, где каждая реализация документирует выбор поведения;
- 3.4.3. неуточняемое поведение (Шаблон:Lang-en) — использование неуточняемого значения или иное поведение, где данный Международный стандарт предоставляет два или более варианта и не налагает никаких других требований на выбор в каждом конкретном случае.
Согласно стандарту языка C++, Шаблон:Начало цитаты
- 1.3.5. поведение, определяемое реализацией (Шаблон:Lang-en) — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации и которое должно быть документировано каждой реализацией;
- 1.3.13. неуточняемое поведение (Шаблон:Lang-en) — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации. Реализация не обязана документировать выбор поведения. [Примечание: как правило, диапазон допустимых поведений указан в данном Международном стандарте.]
Примеры
В Си и C++ (в отличие от языка Java) порядок вычисления параметров функции является неуточняемым; следовательно, в программе, указанной ниже, порядок, в котором будут напечатаны строки «F» и «G», зависит от компилятора.
#include <iostream>
int f() {
std::cout << "F" << std::endl;
return 3;
}
int g() {
std::cout << "G" << std::endl;
return 4;
}
int h(int i, int j) {
return i + j;
}
int main() {
return h(f(), g());
}
Классическим примером поведения, определяемого реализацией (неуточняемого поведения, которое обязано быть документировано реализациями), является размер типов данных; например long в различных компиляторах и операционных системах может быть размером в 32 или 64 бит. Программа, которая предполагает, что в один long всегда поместится указатель, будет некорректно работать на некоторых платформах (например, в Windows x64)[1].
Вот две реализации быстрого обратного квадратного корня: реализация Кармака — Абраша (Quake III) и реализация на Си++20 из английской Википедии:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
constexpr float Q_rsqrt(float number) noexcept
{
static_assert(std::numeric_limits<float>::is_iec559);
float const y = std::bit_cast<float>(
0x5f3759df - (std::bit_cast<std::uint32_t>(number) >> 1));
return y * (1.5f - (number * 0.5f * y * y));
}
Первая сделана для Windows и 32-битного Linux, вторая более универсальна: даёт ошибку компиляции, если на машине нестандартные дробные типы; не требует, чтобы long был 32-битным.
См. также
Примечания
Ссылки
- RSDN FAQ: Какая между ними разница (unspecified и undefined)? Шаблон:Wayback
- Мобильность на уровне исходных текстов Шаблон:Wayback