На реализациях с плоской моделью памяти (в основном все), приведение к uintptr_t
Just Work.
(Но посмотрите, должны ли сравнения указателей быть подписанными или неподписанными в 64-битном x86? Для обсуждения того, следует ли вам рассматривать указатели как подписанные или нет, включая вопросы формирования указателей вне объектов, которые обозначены как UB в C.)
Но системы с неплоскими моделями памяти существуют, и размышления о них могут помочь объяснить текущую ситуацию, например, C ++ имеет разные спецификации для и <
против std::less
.
Часть точки <
указателей на то, чтобы разделить объекты, являющиеся UB в C (или, по крайней мере, не определенные в некоторых ревизиях C ++), состоит в том, чтобы учесть странные машины, включая неплоские модели памяти.
Хорошо известным примером является реальный режим x86-16, где указатели являются сегментами: смещение, образуя 20-битный линейный адрес через (segment << 4) + offset
. Один и тот же линейный адрес может быть представлен несколькими различными комбинациями сегментов.
C ++ std::less
для указателей на странных ISA может быть дорогостоящим , например, «нормализовать» сегмент: смещение на x86-16, чтобы иметь смещение <= 15. Однако нет никакого портативного способа реализовать это. Манипуляции, необходимые для нормализации uintptr_t
(или объектного представления объекта указателя), зависят от реализации.
Но даже в системах, где C ++ std::less
должен быть дорогим, <
не должен быть. Например, предполагая «большую» модель памяти, в которой объект помещается в один сегмент, <
можно просто сравнить смещенную часть и даже не беспокоиться с частью сегмента. (Указатели внутри одного и того же объекта будут иметь один и тот же сегмент, а в противном случае это UB в C. C ++ 17 заменен на просто «неопределенный», что может все же позволить пропустить нормализацию и просто сравнить смещения.) Это предполагает, что все указатели на любую часть объекта всегда использовать одно и то же seg
значение, никогда не нормализуя. Это то, что вы ожидаете от ABI для «большой» модели в отличие от «огромной» модели памяти. (См. Обсуждение в комментариях ).
(Такая модель памяти может иметь максимальный размер объекта, например, 64 КБ, но гораздо большее максимальное общее адресное пространство, в котором есть место для многих таких объектов максимального размера. 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-битное число.