Страницы

Поиск по вопросам

четверг, 20 декабря 2018 г.

Что такое trap representation?

В комментариях к ответам на этот вопрос. Был упомянут термин «trap representation»:
«Даже при использовании дополнительного кода реализациям разрешается резервировать значение −263 в качестве trap representation. Поэтому формально представимость −263 — не гарантируется даже в дополнительном коде» (@AnT).
Что это такое?
P. S. Я нашел ответ на enSO, но там, понятное дело, на английском.


Ответ

Trap representation - это комбинация байтов в объектном представлении какого-либо типа T, которая не обязательно является корректным представлением какого-либо значения типа T. Попытка работы с таким представлением как со значением типа T приводит к неопределенному поведению.
Предпосылок, из-за которых в представлении типа могут существовать trap representations существует много разных. Не претендуя на полноту:
Нетривиальная структура объектного представления типа
Нетривиальная структура внутреннего представления обычно ведет к возникновению внутренних инвариантов, нарушение которых порождает trap representation. Например, объектное представление даже простейших типов может содержать биты четности, предназначенные для проверки целостности данных. Значение, в котором нарушены инварианты четности, задаваемые этими битами - это trap representation.
Нетривиальная структура внутреннего представления также приводит к том, что часть объектных представлений натуральным образом получаются просто "ненужными", бессмысленными. Они являются прямыми кандидатами на trap representations. Например, среди представлений значений плавающих типов IEEE 754 есть комбинации битов, которые являются представлениями signaling NaN ("Not-a-Number" - "не число"). При соответствующей конфигурации FPU попытка работы с такими значениями будет мгновенно приводить к исключению. Это тоже классический пример trap representation. Специально зарезервированные значения
Реализации имеют право по своему усмотрению отказываться работать с некоторыми, на первый взгляд "невинными" представлениями достаточно тривиальных типов. Например, платформы, использующие для представления отрицательных целых чисел прямой (signed magnitude) или обратный (1's-complement) код, имеют право отказываться работать с представлением "отрицательного нуля", рассматривая его как trap representation. Платформы, использующие дополнительный код, имеют право рассматривать битовое представление 100...0 как trap representation. Представления, корректность которых зависит от внешних факторов
В этих случаях trap representations не являются заранее фиксированными. Например, значения указателей, которые не указывают на доступную программе память, могут рассматриваться платформой как trap representations, т.е. вызывать неопределенное поведение уже при обращении к такому значению указателя, еще до того, как мы попытаемся обратиться к самой памяти. Например, на платформе с сегментной организацией памяти уже сама попытка загрузки в сегментный регистр неправильного значения дескриптора сегмента может вызывать падение программы. Совершенно аналогичным образом как trap representation могут рассматриваться значения разнообразных тесно интегрированных в платформу хэндлов, дескрипторов и т.п. Намеренная симуляция trap representation для отладочных целей
Многие современные реализации предоставляют средства "санитизации" отладочной версии кода. Например, попытка чтения значения неинициализированной переменной может приводить в немедленному прерыванию программы с выдачей диагностического сообщения. Это поведение - пример искусственно и намеренно созданного trap representation на уровне software.
В то же время некоторые аппаратные архитектуры поддерживают аналогичную функциональность на аппаратном уровне. Например, архитектура Itanium поддерживает специальное состояние аппаратных регистров - NaT ("not-a-thing", "ничто"), которое соответствует именно trap represеntation в терминологии языка. (Это состояние предназначено в первую очередь для обозначения неинициализированности регистра.)
Единственным фундаментальным объектным представлением, для которого гарантируется отсутствие trap representations, является представление значений типа unsigned char (и типа char в реализациях, где char является беззнаковым типом). Благодаря этому любые объекты можно переинтерпретировать как массивы unsigned char [] и смело просматривать содержимое таких массивов, не боясь наткнуться на trap representation.
Представление объекта типа struct или union никогда не считается trap representation, даже если одно из его полей содержит trap representation. Т.е. чтение содержимого такого struct/union объекта как с единого целого не приводит к неопределенному поведению, в то время как чтение конкретного поля, содержащего trap representation - приводит.

P.S. На связанную тему:
В стандарте С89/90 чтение неинициализированного значения всегда приводило к неопределенному поведению. В стандарте С99 эта часть спецификации изменилась: в С99 неинициализированная переменная теперь содержит либо неспецифицированное значение, либо trap representation. В случае trap representation возникает неопределенное поведение, а вот в случае неспецифицированного значения неопределенного поведения не возникает. Как сказано выше, тип unsigned char не имеет trap representations. То есть получилось так, что в С99 чтение неинициализированного значения типа unsigned char просто дает неспецифицированное значение и гарантированно не вызывает неопределенного поведения.
Это, однако, идет вразрез с изначальными намерениями авторов стандарта.
Во-первых, требование того, чтобы тип unsigned char не имел trap representations, было введено для того, чтобы через массивы unsigned char [] можно было просматривать "бинарное" содержимое других объектов в памяти, а отнюдь не для того, чтобы переменные типа unsigned char можно было "забывать" инициализировать.
Во-вторых, это создало проблемы с архитектурой Itanium и ее NaT: если реализация для платформы Itanium захочет хранить локальную переменную типа unsigned char в регистре, то этим реализациям придется насильно инициализировать такие регистры, чтобы вывести их из состояния NaT, что идет вразрез с традиционно принятым в С подходом.
В конечном итоге было решено еще дополнительно подправить спецификацию языка в версии С11 (раздел 6.3.2.1/2): если вся работа с локальной переменной в коде соответствует требованиям класса хранения register (т.е. переменная потенциально может храниться в регистре), то чтение неинициализированного значения такой переменной однозначно приводит к неопределенному поведению. Это касается как неинициализированных переменных типа unsigned char так и неинициализированных переменных типа struct
В частности, эта часть спецификации иллюстрируется довольно странными примерами
void foo() { unsigned char junk; unsigned char a = junk + 1; // неопределенное поведение
struct { int i; } a, b; a = b; // неопределенное поведение }
void bar() { unsigned char junk; &junk; unsigned char a = junk + 1; // неспецифицированное значение
struct { int i; } a, b; &b; a = b; // неспецифицированное значение }

Комментариев нет:

Отправить комментарий