Резюме

Не пытайтесь делать какие-то предположения о том, как именно объекты представлены в памяти. Как именно следует записывать и считывать объекты из памяти — пусть решают типы объектов.

Обсуждение

Стандарт С++ дает очень мало гарантий по поводу представления типов в памяти.

• Целые числа используют двоичное представление.

• Для отрицательных чисел используется дополнительный код числа в двоичной системе.

• Обычные старые типы (Plain Old Data, POD[5]) имеют совместимое с С размещение в памяти: переменные-члены хранятся в порядке их объявления.

• Тип int занимает как минимум 16 битов.

В частности, достаточно распространенные соглашения не гарантированы ни для всех имеющихся архитектур, ни тем более для архитектур, которые могут появиться в будущем. Так что не забывайте о следующем.

• Размер int не равен ни 32 битам, ни какому-либо иному фиксированному размеру.

• Указатели и целые числа не всегда имеют один и тот же размер и не могут свободно преобразовываться друг в друга.

• Размещение класса в памяти не всегда приводит к размещению базового класса и членов в указанном порядке.

• Между членами класса (даже если они являются POD) могут быть промежутки в целях выравнивания.

• offsetof работает только для POD, но не для всех классов (хотя компилятор может и не сообщать об ошибках).

• Класс может иметь скрытые поля.

• Указатели могут быть совсем не похожи на целые числа. Если два указателя упорядочены и вы можете преобразовать их в целые числа, то получающиеся значения могут быть упорядочены иначе.

• Нельзя переносимо полагаться на конкретное размещение автоматических переменных в памяти или на направление роста стека.

• Указатели на функции могут иметь размер, отличный от размера указателя void*, несмотря на то, что некоторые API заставляют вас предположить, что их размеры одинаковы.

• Из-за вопросов выравнивания вы не можете записывать ни один объект по произвольному адресу в памяти.

Просто корректно определите типы, а затем читайте и записывайте данные с использованием указанных типов вместо работы с отдельными битами, словами и адресами. Модель памяти С++ гарантирует эффективную работу, не заставляя вас при этом работать с представлениями данных в памяти. Так и не делайте этого.

Ссылки

[Dewhurst03] §95

92. Избегайте reinterpret_cast

Резюме

Как гласит римская пословица, у лжи короткие ноги. Не пытайтесь использовать reinterpret_cast, чтобы заставить компилятор рассматривать биты объекта одного типа как биты объекта другого типа. Такое действие противоречит безопасности типов.

Обсуждение

Вспомните: Если вы лжете компилятору, он будет мстить (Генри Спенсер).

Преобразование reinterpret_cast отражает представления программиста о представлении объектов в памяти, т.е. программист берет на себя ответственность за то, что он лучше компилятора знает, что можно и что нельзя. Компилятор молча сделает то, что вы ему скажете, но применять такую грубую силу в отношениях с компилятором — последнее дело. Избегайте каких-либо предположений о представлении данных, поскольку такие предположения очень сильно влияют на безопасность и надежность вашего кода.

Кроме того, реальность такова, что результат применения reinterpret_cast еще хуже, чем просто насильственная интерпретация битов объекта (что само по себе достаточно нехорошо). За исключением некоторых гарантированно обратимых преобразований результат работы reinterpret_cast зависит от реализации, так что вы даже не знаете точно, как именно он будет работать. Это очень ненадежное и непереносимое преобразование.

Исключения

Некоторые низкоуровневые специфичные для данной системы программы могут заставить вас применить reinterpret_cast к потоку битов, проходящих через некоторый порт, или для преобразования целых чисел в адреса. Используйте такое небезопасное преобразование как можно реже и только в тщательно скрытых за абстракциями функциях, чтобы ваш код можно было переносить с минимальными изменениями. Если вам требуется преобразование между указателями несвязанных типов, лучше выполнять его через приведение к void* вместо непосредственного использования reinterpret_cast, т.е. вместо кода

T1* p1 = ... ;

T2* p2 = reinterpret_cast<T2*>(p1);

лучше писать

T1* p1 = ...;

void* pV = p1;

T2* p2 = static_cast<T2*>(pV);

Ссылки

[С++03] §5.2.10(3) • [Dewhurst03] §39 • [Stroustrup00] §5.6

93. Избегайте применения static_cast к указателям

Резюме

К указателям на динамические объекты не следует применять преобразование static_cast. Используйте безопасные альтернативы — от dynamic_cast до перепроектирования.

Обсуждение

Подумайте о замене static_cast более мощным оператором dynamic_cast, и вам не придется запоминать, в каких случаях применение static_cast безопасно, а в каких — чревато неприятностями. Хотя dynamic_cast может оказаться немного менее эффективным преобразованием, его применение позволяет обнаружить неверные преобразования типов (но не забывайте о рекомендации 8). Использование static_cast вместо dynamic_cast напоминает экономию на ночном освещении, когда выигрыш доллара в год оборачивается переломанными ногами.

При проектировании постарайтесь избегать понижающего приведения. Перепроектируйте ваш код таким образом, чтобы такое приведение стало излишним. Если вы видите, что передаете в функцию базовый класс там, где в действительности потребуется производный класс, проследите всю цепочку вызовов, чтобы понять, где же оказалась потерянной информация о типе; зачастую изменение пары прототипов оказывается замечательным решением, которое к тому же делает код более простым и

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату