Неуточнённое поведение () и поведение, определяемое реализацией () — поведение компьютерной программы, которое может различаться на разных платформах и компиляторах, поскольку спецификация языка программирования предлагает несколько допустимых вариантов реализации некой языковой конструкции. В отличие от неопределённого поведения, программа с неуточнённым поведением с точки зрения соответствия спецификации языка не считается ошибочной; при неуточняемом поведении, спецификация обычно ограничивает возможные варианты поведения, хотя и не сводит их в единое допустимое.

Разница между тем и другим такая: поведение, определяемое реализацией, задокументированное и последовательное на данном процессоре, программном окружении, версии системы и т. д. Неуточнённое поведение может меняться от случая к случаю, но система обязательно сделает что-то разумное — а не уйдёт в аварийный режим.

Программист должен избегать: Неопределённого поведения — всегда. Пример: доступ по NULL-указателю недопустим. Неуточнённого поведения — там, где оно критично для результата программы. Пример: если две функции вызываются в неуточнённом порядке и в них общий отладочный код, это будет видно в отладочном журнале, но для результата может быть и не критично. Но: если реализация уточняет неопределённое или неуточнённое поведение, программист может на неё закладываться. Примеры: хоть в Си переполнение знакового типа — это неопределённое поведение, на большинстве современных архитектур 32767+1=−32768. Если доступ по NULL-указателю обращается к вектору прерывания 0, или намеренно вызывает общую аварию программы, то это можно делать. Поведения, определяемого реализацией — если в числе поддерживаемых платформ есть такие, что ведут себя по-разному. Пример: большинство 8- и 16-битных платформ (в основном микроконтроллеры и старые компьютеры) говорят, что целый тип int — это два байта, но если поддерживаем только сравнительно мощные машины, можно считать, что int — четыре байта.

Терминология Согласно стандарту языка C99,

3.4.1. поведение, определяемое реализацией () — неуточняемое поведение, где каждая реализация документирует выбор поведения; 3.4.3. неуточняемое поведение () — использование неуточняемого значения или иное поведение, где данный Международный стандарт предоставляет два или более варианта и не налагает никаких других требований на выбор в каждом конкретном случае.

Согласно стандарту языка C++,

1.3.5. поведение, определяемое реализацией () — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации и которое должно быть документировано каждой реализацией; 1.3.13. неуточняемое поведение () — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации. Реализация не обязана документировать выбор поведения. [Примечание: как правило, диапазон допустимых поведений указан в данном Международном стандарте.]

Примеры В Си и 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).

Вот две реализации быстрого обратного квадратного корня: реализация Кармака — Абраша (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)? Мобильность на уровне исходных текстов

Категория:Ошибки программирования Категория:Концепции языков программирования