На реалізаціях з плоскою моделлю пам'яті (в основному все), передавайте на uintptr_t
Will 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-бітове число.