В реализациях с режимом плоской памяти (в основном все) приведение к uintptr_t
будет просто работать.
(Но см. Должны ли сравнения указателей быть подписанными или неподписанными в 64-битной x86? для обсуждения того, следует ли вам рассматривать указатели как подписанные или нет, включая вопросы формирования указателей вне объектов, что является UB в C.)
Но системы с неплоскими моделями памяти существуют,и размышления о них могут помочь объяснить текущую ситуацию, например, C ++ имеет различные спецификации для <
против std::less
.
Часть точки <
на указателях для разделенияобъекты, являющиеся UB в C (или, по крайней мере, не определенные в некоторых ревизиях C ++), должны учитывать странные машины, включая не плоские модели памяти.
Хорошо известным примером является реальный режим x86-16, где указатели являются сегментными: смещение, формирующее 20-битный линейный адрес через (segment << 4) + offset
. Один и тот же линейный адрес может быть представлен несколькими различными комбинациями seg: off.
C ++ std::less
для указателей на странных ISA может потребовать больших затрат , например, "нормализовать" сегмент: смещениена x86-16 иметь смещение <= 15. Тем не менее, <em>portable не существует способа реализовать это. Манипуляции, необходимые для нормализации uintptr_t
(или объектного представления объекта-указателя), зависят от реализации.
Но даже в системах, где C ++ std::less
должен быть дорогим, <
не должно быть. Например, предполагая «большую» модель памяти, в которой объект помещается в пределах одного сегмента, <
может просто сравнить смещенную часть и даже не беспокоиться с частью сегмента. (Указатели внутри одного и того же объекта будут иметь один и тот же сегмент, и в противном случае это UB в C. C ++ 17 заменен на просто «неопределенный», что может все же позволить пропустить нормализацию и просто сравнить смещения.) Это предполагает, что все указатели на любую частьобъекта всегда использовать то же значение seg
, никогда не нормализуя. Это то, что вы ожидаете от ABI для «большой» модели в отличие от «огромной» модели памяти. (См. обсуждение в комментариях ).
(Такая модель памяти может иметь максимальный размер объекта, например, 64 КБ, но гораздо большее максимальное общее адресное пространство, в котором есть место для многих таких max. объекты размера ISO. C позволяет реализациям иметь ограничение на размер объекта, которое меньше максимального значения (без знака), которое может представлять size_t
, SIZE_MAX
. Например, даже в системах с плоской памятью GNU C ограничивает максимальный размер объектаPTRDIFF_MAX
чтобы при расчете размера можно было игнорировать переполнение со знаком.) См. этот ответ и обсуждение в комментариях.
Если вы хотите разрешить объекты размером больше сегмента, вам нужна "огромная" памятьмодель, которая должна беспокоиться о переполнении смещенной части указателя при выполнении p++
для циклического перемещения по массиву или при выполнении арифметики индексирования / указателя. Это повсеместно приводит к более медленному коду, но, вероятно, будет означать, что p < q
будет работать для указателей на разные объекты, потому что реализация, нацеленная на «огромную» модель памяти, обычно предпочитает постоянно поддерживать все указатели нормализованными. См. Что такое ближний, дальний и огромный указатели? - некоторые настоящие компиляторы C для реального режима x86 имели возможность компилировать для "огромной" модели, где все указатели по умолчанию установлены в "огромный", если не указано иное.
x86 Сегментация в реальном режиме - не единственная возможная модель неплоской памяти , это всего лишь полезный конкретный пример, иллюстрирующий, как она обрабатывается реализациями C / C ++. В реальной жизни реализации расширили ISO C с концепцией указателей far
против near
, что позволяет программистам выбирать, когда им удастся просто сохранить / передать 16-битную часть смещения относительно некоторого общего сегмента данных.
Но для реализации в чистом ISO C придется выбирать между маленькой моделью памяти (все, кроме кода в том же 64-килобайтном формате с 16-разрядными указателями) или большой или огромной, причем все указатели являются 32-разрядными. Некоторые циклы можно оптимизировать, увеличивая только смещенную часть, но объекты указателя нельзя оптимизировать, чтобы они были меньше.
Если вы знали, что такое магическая манипуляция для любой данной реализации, вы могли быреализовать это в чистом C . Проблема в том, что разные системы используют разную адресацию, а детали не параметризуются никакими переносимыми макросами.
Или, возможно, нет: это может включать поиск чего-либо из специальной таблицы сегментов или что-то подобное, например, как защищенный режим x86вместо реального режима, где сегментная часть адреса представляет собой индекс, а не значение, смещаемое влево. Вы можете настроить частично перекрывающиеся сегменты в защищенном режиме, и части адресов сегментов селектора не обязательно будут упорядочены в том же порядке, что и соответствующие базовые адреса сегментов. Для получения линейного адреса из указателя seg: off в защищенном режиме x86 может потребоваться системный вызов, если GDT и / или LDT не отображаются на читаемые страницы вашего процесса.
(Конечно, основные ОС дляx86 использует плоскую модель памяти, поэтому база сегмента всегда равна 0 (за исключением локального хранилища потока, использующего сегменты fs
или gs
), и только 32-битная или 64-битная часть «смещения» используется в качестве указателя.)
Вы можете вручную добавить код для различных конкретных платформ, например, по умолчанию предположить, что он плоский, или #ifdef
что-то для обнаружения реального режима x86 и разделить uintptr_t
на 16-битные половины для seg -= off>>4; off &= 0xf;
, а затем объединитьэти части возвращаются в 32-битное число.