Неуточнённое поведение

(перенаправлено с «Неуточняемое поведение»)

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

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

Программист должен избегать:

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

Терминология править

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

  • 3.4.1. поведение, определяемое реализацией (англ. implementation-defined behavior) — неуточняемое поведение, где каждая реализация документирует выбор поведения;
  • 3.4.3. неуточняемое поведение (англ. unspecified behavior) — использование неуточняемого значения или иное поведение, где данный Международный стандарт предоставляет два или более варианта и не налагает никаких других требований на выбор в каждом конкретном случае.
ISO/IEC 9899:201x [1]

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

  • 1.3.5. поведение, определяемое реализацией (англ. implementation-defined behavior) — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации и которое должно быть документировано каждой реализацией;
  • 1.3.13. неуточняемое поведение (англ. unspecified behavior) — поведение правильно построенной программной конструкции с правильными данными которое зависит от реализации. Реализация не обязана документировать выбор поведения. [Примечание: как правило, диапазон допустимых поведений указан в данном Международном стандарте.]
ISO/IEC 14882:2003(E)

Примеры править

В Си и 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)[2].

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

См. также править

Примечания править

  1. ISO/IEC 9899:201x Committee Draft — August 11, 2008 (англ.). Дата обращения: 1 декабря 2009. Архивировано 11 апреля 2012 года.
  2. size of long integer type on different architecture and OS (англ.). Intel Software Network. Дата обращения: 1 декабря 2009. Архивировано 11 апреля 2012 года.

Ссылки править