Что такое тип данных uintptr_t

Ответы:

199

uintptr_tявляется целочисленным типом без знака, который способен хранить указатель данных. Что обычно означает, что это тот же размер, что и указатель.

Опционально определяется в C ++ 11 и более поздних стандартах.

Распространенная причина для того, чтобы получить целочисленный тип, который может содержать тип указателя архитектуры, состоит в том, чтобы выполнять целочисленные операции над указателем или скрывать тип указателя, предоставляя его как целочисленный «дескриптор».

Изменить: Обратите внимание, что у Стива Джессопа есть несколько очень интересных дополнительных деталей (которые я не буду красть) в другом ответе здесь для вас педантичных типов :)

Дрю Дорманн
источник
53
Обратите внимание, что он size_tдолжен быть достаточным для хранения размера наибольшего объекта и может быть меньше указателя. Этого можно ожидать на сегментированных архитектурах, таких как 8086 (16 бит size_t, но 32 бит void*)
MSalters
3
для представления разницы между двумя указателями, у вас есть ptrdiff_t. uintptr_tне предназначен для этого.
Джалф
3
@jalf: Для разницы, да, но для расстояния , вы бы хотели беззнаковый тип.
Дрю Дорманн
Определение uintptr_t не является обязательным (поэтому не стандартным) даже в C ++ 11! cplusplus.com/reference/cstdint (я получил подсказку от ответа Стива Джессопа)
Антонио
2
@MarcusJ unsigned intобычно недостаточно велик. Но это может быть достаточно большим. Этот тип существует специально, чтобы удалить все «предположения» .
Дрю Дорманн
239

Во-первых, в то время, когда задавался вопрос, uintptr_tне было в C ++. Это в C99, в <stdint.h>качестве дополнительного типа. Многие компиляторы C ++ 03 предоставляют этот файл. Это также в C ++ 11, <cstdint>где, опять же, это необязательно, и который ссылается на C99 для определения.

В C99 это определяется как «целочисленный тип без знака со свойством, что любой действительный указатель на void может быть преобразован в этот тип, затем преобразован обратно в указатель на void, и результат будет сравниваться равным исходному указателю».

Примите это, чтобы означать, что это говорит. Это ничего не говорит о размере.

uintptr_tможет быть такого же размера, как void*. Это может быть больше. Вероятно, он может быть меньше, хотя такая реализация на C ++ подходит извращенно. Например, на некоторой гипотетической платформе, где void*используется 32 бита, но используется только 24 бита виртуального адресного пространства, у вас может быть 24-бит, uintptr_tкоторый удовлетворяет требованию. Я не знаю, почему реализация сделала бы это, но стандарт разрешает это.

Стив Джессоп
источник
8
Спасибо за "<stdint.h>". Мое приложение не скомпилировано из-за объявления uintptr_t. Но когда я читаю ваш комментарий, я добавляю "#include <stdint.h>", и да, теперь это работает. Спасибо!
JavaRunner
2
Для того, чтобы выделить выравненную память , среди других применений, например?
legends2k
4
Другим распространенным случаем приведения указателя на int является создание непрозрачного дескриптора, который скрывает указатель. Это полезно для возврата ссылки на объект из API, где вы хотите сохранить объект закрытым для библиотеки и запретить доступ к нему приложений. Затем приложение вынуждено использовать API для выполнения каких-либо операций над объектом
Джоэл Каннингем
3
@JoelCunningham: это работает, но на самом деле ничем не отличается от использования void*. Это влияет на возможные будущие направления, тем не менее, особенно если вы можете захотеть изменить что-то, что на самом деле является просто целочисленным дескриптором, а не преобразованным указателем вообще.
Стив Джессоп
2
@CiroSantilli 事件 事件 2016 六四 事件 法轮功 Распространенным вариантом использования является передача только int в API, который ожидает пустоту * для общих данных. Спасает вас нажатия клавиш typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;. Вместо этого: callback.dataPtr = (void*)val. С другой стороны, вы, конечно, получаете void*и должны отбросить его обратно int.
Франческо Донди
19

Это целочисленный тип без знака точно такого же размера, как указатель. Всякий раз, когда вам нужно сделать что-то необычное с указателем - как, например, инвертировать все биты (не спрашивайте, почему), вы uintptr_tприводите его и манипулируете им как обычным целым числом, а затем возвращаете.

Sharptooth
источник
6
Конечно, вы могли бы сделать это, но это, конечно, было бы неопределенным поведением. Я полагаю, что единственное, что вы можете сделать с результатом приведения к uintptr_t, это передать его без изменений и привести обратно - все остальное - UB.
Слёске
Есть моменты, когда вам нужно поиграть с битами, и это обычно приводит к ошибкам компилятора. Типичным примером является принудительное выравнивание 16-байтовой памяти для определенных приложений, критичных для видео и производительности. stackoverflow.com/questions/227897/…
Облако
3
@ Слеске, это не правда. На машинах с самоустанавливающимися типами два младших значащих бита указателя будут равны нулю (поскольку адреса кратны 4 или 8). Я видел программы, которые используют это для сжатия данных ..
saadtaame
3
@saadtaame: Я просто указал, что это UB в соответствии со стандартом C . Это не означает, что он не может быть определен в некоторых системах - компиляторы и среды выполнения могут свободно определять конкретное поведение для чего-то, что является UB в стандарте C. Так что нет никакого противоречия :-).
слеске
2
Это не обязательно точно размер указателя. Все стандартные гарантии состоят в том, что преобразование void*значения указателя в uintptr_tи обратно возвращает void*значение, которое сравнивается с исходным указателем. uintptr_tобычно имеет такой же размер, как и void*, но это не гарантируется, и нет никакой гарантии, что биты преобразованного значения имеют какое-то конкретное значение. И нет никакой гарантии, что он может содержать преобразованное значение указателя на функцию без потери информации. Наконец, это не гарантировано.
Кит Томпсон
15

Уже есть много хороших ответов на часть "что такое тип данных uintptr_t". Я попытаюсь ответить на вопрос "для чего это может быть использовано?" часть в этом посте.

В первую очередь для побитовых операций с указателями. Помните, что в C ++ нельзя выполнять побитовые операции с указателями. По причинам см. Почему вы не можете выполнять побитовые операции с указателем в C, и есть ли способ обойти это?

Таким образом, чтобы выполнять побитовые операции с указателями, нужно привести указатели к типу unitpr_t, а затем выполнить побитовые операции.

Вот пример функции, которую я только что написал для выполнения побитового исключения или двух указателей для хранения в связанном списке XOR, чтобы мы могли перемещаться в обоих направлениях, как список с двумя связями, но без штрафа за сохранение двух указателей в каждом узле ,

 template <typename T>
 T* xor_ptrs(T* t1, T* t2)
 {
     return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
  }
BGR
источник
1
кроме битовой арифметики также хорошо, если вы хотите иметь семантику, основанную на адресах, а не на количестве объектов.
Алекс
4

Риск получить еще один значок Necromancer, я хотел бы добавить одно очень хорошее использование для uintptr_t (или даже intptr_t), и это написание тестируемого встроенного кода. Я пишу в основном встроенный код, предназначенный для различных процессоров, а в настоящее время - процессоров. Они имеют различную внутреннюю ширину шины, а tenisilica - это фактически гарвардская архитектура с отдельным кодом и шинами данных, которые могут быть различной ширины. Я использую стиль разработки, основанный на тестировании, для большей части своего кода, что означает, что я делаю модульные тесты для всех блоков кода, которые я пишу. Модульное тестирование на реальном целевом оборудовании является проблемой, поэтому я обычно пишу все на ПК на базе Intel в Windows или Linux с использованием Ceedling и GCC. Это, как говорится, много встроенного кода включает в себя битовый твидинг и манипулирование адресами. Большинство моих машин Intel являются 64-битными. Так что, если вы собираетесь тестировать код манипулирования адресами, вам нужен обобщенный объект для математики. Таким образом, uintptr_t дает вам независимый от машины способ отладки кода перед тем, как вы попытаетесь развернуть его на целевом оборудовании. Другая проблема заключается в том, что для некоторых машин или даже моделей памяти в некоторых компиляторах указатели функций и указатели данных имеют разную ширину. На этих машинах компилятор может даже не разрешить приведение между двумя классами, но uintptr_t должен быть в состоянии удерживать любой из них.

bd2357
источник
Не уверен, если уместно ... Вы пробовали "непрозрачные typedefs"? Видео: youtube.com/watch?v=jLdSjh8oqmE
shycha
@sleske Я бы хотел, чтобы это было доступно на C. Но иметь stdint.h лучше, чем ничего. (Также хотелось бы, чтобы перечисления были напечатаны сильнее, но большинство отладчиков отлично справляются с задачей)
bd2357