На реалізаціях з плоскою моделлю пам'яті (в основному все), передавайте на uintptr_tWill Just Work.
(Але дивіться Чи слід порівняння покажчиків підписувати чи не підписати в 64-бітному x86? Для обговорення того, чи слід ставитися до покажчиків як до підписаних чи ні, включаючи питання формування покажчиків поза об'єктами, які є UB у C.)
Але системи з неплоскими моделями пам'яті існує, і думати про них можуть допомогти пояснити поточну ситуацію, як C ++ , що мають різні функції для <VS. std::less.
Частина точки <покажчиків на окремі об'єкти, що є UB в C (або, принаймні, не вказані в деяких версіях C ++), полягає в тому, щоб створити дивні машини, включаючи не плоскі моделі пам'яті.
Добре відомий приклад - реальний режим x86-16, де покажчики сегментуються: зміщення, утворюючи 20-бітну лінійну адресу через (segment << 4) + offset. Одна і та ж лінійна адреса може бути представлена кількома різними комбінаціями seg: off.
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-бітове число.