Що таке uintptr_t
і для чого його можна використовувати?
Що таке uintptr_t
і для чого його можна використовувати?
Відповіді:
uintptr_t
це цілочисельний тип без підпису, здатний зберігати покажчик даних. Що зазвичай означає, що він такого ж розміру, як і вказівник.
Це необов'язково визначено в C ++ 11 та пізніших стандартах.
Загальна причина, щоб хотіти цілочисельний тип, який може містити тип вказівника архітектури, - це виконувати цілочисельні операції над покажчиком або затемнити тип покажчика, надаючи його як цілочислену "ручку".
Редагувати: Зауважте, що у Стіва Джессопа є кілька дуже цікавих додаткових деталей (які я не буду красти) в іншій відповіді тут для вас педантичних типів :)
size_t
потрібно лише бути достатньою для розміру найбільшого розміру об'єкта і може бути меншою за покажчик. Цього можна було б очікувати на сегментованих архітектурах, таких як 8086 (16 біт size_t
, але 32 біт void*
)
ptrdiff_t
. uintptr_t
не призначений для цього.
unsigned int
зазвичай недостатньо великий. Але це може бути досить великим. Цей тип існує спеціально для видалення всіх "припущень" .
По-перше, на той момент питання uintptr_t
було не в C ++. Це в C99, в <stdint.h>
якості додаткового типу. Цей файл надає багато компіляторів C ++ 03. Він також знаходиться в C ++ 11, в <cstdint>
, де знову це необов’язково, і який для визначення визначає C99.
У C99 він визначається як "цілий цільовий підпис без властивості, властивість якого будь-який дійсний вказівник на недійсну може бути перетворений у цей тип, потім перетворений назад у покажчик на недійсний, і результат порівняється рівним вихідному вказівнику".
Вживайте це, щоб означати, що воно говорить. Про розмір це нічого не говорить.
uintptr_t
може бути такого ж розміру, як і void*
. Він може бути більшим. Він може бути меншим, хоча така реалізація C ++ підходить до викривлення. Наприклад, на якійсь гіпотетичній платформі, де void*
є 32 біти, але використовується лише 24 біти віртуального адресного простору, у вас може бути 24-розрядний, uintptr_t
який задовольняє вимогу. Я не знаю, чому реалізація зробила б це, але стандарт це дозволяє.
void*
. Це впливає на можливі майбутні напрямки, особливо, якщо ви хочете змінити, щоб використовувати щось, що насправді є лише цілою цілою ручкою, а не перетвореним покажчиком.
typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;
. Замість цього: callback.dataPtr = (void*)val
. З іншого боку, ви, звичайно, отримаєте void*
і вам доведеться кинути його назад int
.
Це непідписаний цілочисельний тип точно за розміром вказівника. Кожен раз, коли вам потрібно зробити щось незвичне з покажчиком - наприклад, інвертувати всі біти (не запитуйте, чому), ви кидаєте на нього uintptr_t
та маніпулюєте ним як звичайне ціле число, а потім повертаєте назад.
void*
значення вказівника на uintptr_t
знову і назад дає void*
значення, яке порівнюється з оригінальним вказівником. uintptr_t
зазвичай має той самий розмір, що void*
, але це не гарантується, і не існує жодної гарантії того, що біти перетвореного значення мають якесь особливе значення. І немає гарантії, що він може вмістити перетворене значення вказівника на функцію без втрати інформації. Нарешті, це не гарантується.
У частині "що таке тип даних uintptr_t" вже існує багато хороших відповідей. Я спробую розглянути питання про те, "для чого це можна використовувати?" участь у цій публікації.
В основному для побітових операцій на покажчиках. Пам'ятайте, що в C ++ ніхто не може виконувати побітові операції над покажчиками. Про причини див. Чому ви не можете робити побітові операції над покажчиком на C, і чи існує спосіб цього?
Таким чином, для того, щоб робити побітові операції на покажчиках, потрібно буде навести покажчики на тип unitpr_t, а потім виконати побітові операції.
Ось приклад функції, яку я щойно написав, щоб робити побітові ексклюзивні або 2 покажчики на зберігання у списку, пов’язаному з XOR, щоб ми могли переходити в обох напрямках, як подвійно пов'язаний список, але без штрафу зберігання 2 покажчиків у кожному вузлі .
template <typename T>
T* xor_ptrs(T* t1, T* t2)
{
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
}
Рискуючи отримати ще один знак Necromancer, я хотів би додати одне дуже вдале використання для uintptr_t (або навіть intptr_t), і це написання тестового вбудованого коду. Я пишу здебільшого вбудований код, орієнтований на різні процесори та в даний час тенісилики. Вони мають різну ширину шини, і тенісиліка - це фактично гарвардська архітектура з окремими шинами коду та даних, які можуть бути різної ширини. Я використовую тестовий стиль розробки для більшої частини свого коду, що означає, що я роблю одиничні тести для всіх кодових одиниць, які я пишу. Тестування блоків на фактичному цільовому обладнанні - це клопот, тому я, як правило, записую все на ПК на базі Intel в Windows або Linux, використовуючи Ceedling та GCC. Зазначається, що багато вбудованого коду включає подвійне скручування та маніпуляції з адресою. Більшість моїх машин Intel 64-бітні. Отже, якщо ви збираєтеся перевірити код маніпуляції адресою, вам потрібен узагальнений об'єкт, щоб зробити математику. Таким чином, uintptr_t надає машинному незалежному способу налагодження коду перед тим, як спробувати розгорнути цільове обладнання. Інша проблема стосується деяких машин або навіть моделей пам'яті деяких компіляторів, функціональні покажчики та покажчики даних різної ширини. На цих машинах компілятор може навіть не допускати кастингу між двома класами, але uintptr_t повинен мати змогу утримувати будь-який.