Какая польза от intptr_t?
Я знаю, что это целочисленный тип, который может быть приведен к / от указателя без потери данных, но зачем мне это когда-либо хотеть? Какое преимущество имеет целочисленный тип по сравнению с void*
для удерживания указателя и THE_REAL_TYPE*
для арифметики указателей?
РЕДАКТИРОВАТЬ
Вопрос, помеченный как "уже задан", на это не отвечает. Вопрос есть, если использовать intptr_t
в качестве общей замены для void*
это хорошая идея, и ответы там, кажется, "не используйте intptr_t", поэтому мой вопрос остается в силе: что было бы хорошим вариантом использования для intptr_t
?
6 ответов
Основная причина, вы не можете сделать побитовую операцию на void *
, но вы можете сделать то же самое на intptr_t
,
Во многих случаях, когда вам нужно выполнить побитовую операцию с адресом, вы можете использовать intptr_t
,
Однако для побитовых операций наилучшим подходом является использование unsigned
коллега, uintptr_t
,
Как упоминалось в другом ответе chux, сравнение указателей является еще одним важным аспектом.
Кроме того, FWIW, в соответствии с C11
стандарт, §7.20.1.4,
Эти типы не являются обязательными.
Там также семантическое соображение.
void*
должен указывать на что-то. Несмотря на современную практичность, указатель не является адресом памяти. Хорошо, обычно / вероятно / всегда (!) Содержит один, но это не число. Это указатель. Это относится к вещи.
intptr_t
не. Это целочисленное значение, которое безопасно конвертировать в / из указателя, чтобы вы могли использовать его для античных API, упаковав его в pthread
аргумент функции и тому подобное.
Вот почему вы можете делать больше чисел и мелочей на intptr_t
чем вы можете на void*
и почему вы должны самодокументироваться, используя правильный тип для работы.
В конечном счете, почти все может быть целым числом (помните, ваш компьютер работает с числами!). Указатели могли быть целыми числами. Но это не так. Они указатели, потому что они предназначены для различного использования. И, теоретически, они могут быть чем-то отличным от цифр.
Тип uintptr_t очень полезен при написании кода управления памятью. Такой код хочет общаться со своими клиентами в терминах общих указателей (void *), но внутренне выполняет все виды арифметики для адресов.
Вы можете сделать то же самое, используя char *, но не все, и результат выглядит как до Ansi C.
Не весь код управления памятью использует uintptr_t - например, код ядра BSD определяет vm_offset_t с аналогичными свойствами. Но если вы пишете, например, пакет отладки malloc, зачем придумывать свой собственный тип?
Это также полезно, когда у вас есть% p в вашем printf, и вы пишете код, который должен печатать целочисленные переменные размером с указатель в шестнадцатеричном формате на различных архитектурах.
Я нахожу intptr_t несколько менее полезным, за исключением, возможно, в качестве промежуточной станции при приведении, чтобы избежать ужасного предупреждения об изменении подписи и целочисленного размера в одном и том же приведении. (Написание переносимого кода, который передает -Wall -Werror на всех соответствующих архитектурах, может быть сложной задачей.)
Какая польза от intptr_t?
Пример использования: сравнение заказов.
Сравнение указателей на равенство не является проблемой.
Другие операции сравнения, такие как >, <=
может быть UB. C11dr §6.5.8/5 Реляционные операторы.
Так что конвертировать в intptr_t
первый.
[Редактировать] Новый пример: сортировка массива указателей по значению указателя.
int ptr_cmp(const void *a, const void *b) {
intptr_t ia = (intptr) (*((void **) a));
intptr_t ib = (intptr) (*((void **) b));
return (ia > ib) - (ia < ib);
}
void *a[N];
...
qsort(a, sizeof a/sizeof a[0], sizeof a[0], ptr_cmp);
[Бывший пример] Пример использования: проверить, является ли указатель массивом указателей.
#define N 10
char special[N][1];
// UB as testing order of pointer, not of the same array, is UB.
int test_special1(char *candidate) {
return (candidate >= special[0]) && (candidate <= special[N-1]);
}
// OK - integer compare
int test_special2(char *candidate) {
intptr_t ca = (intptr_t) candidate;
intptr_t mn = (intptr_t) special[0];
intptr_t mx = (intptr_t) special[N-1];
return (ca >= mn) && (ca <= mx);
}
Как прокомментировал @MM, приведенный выше код может работать не так, как задумано. Но по крайней мере это не UB. - просто не переносимая функциональность. Я надеялся использовать это, чтобы решить эту проблему.
(u)intptr_t
используется, когда вы хотите выполнять арифметические операции с указателями, особенно побитовые операции. Но, как говорили другие, вы почти всегда захотите использовать
uintptr_t
потому что побитовые операции лучше выполнять без знака. Однако, если вам нужно выполнить арифметический сдвиг вправо, вы должны использовать
intptr_t
. Обычно он используется для хранения данных в указателе, обычно называемом указателем с тегами.
В x86-64 вы можете использовать старшие биты 16/7 для данных, но вы должны сделать расширение знака вручную, чтобы сделать указатель каноническим, потому что в настоящее время у него нет флага для игнорирования старших битов, как в ARM . Так, например, если у вас есть
char* address
тогда вам нужно будет сделать это перед разыменованиемchar* pointer = (char*)((intptr_t)address << 16 >> 16);
32-битный движок Chrome V8 использует оптимизацию smi (маленькое целое число), где младший бит обозначает тип
|----- 32 bits -----| Pointer: |_____address_____w1| # Address to object, w = weak pointer Smi: |___int31_value____0| # Small integer
Поэтому, когда младший бит указателя равен 0, он будет сдвинут вправо, чтобы получить исходный 31-битный знаковый int.
int v = (intptr_t)address >> 1;
Для получения дополнительной информации прочтите
В 64-битной системе целое число составляет 4 байта (32 бита), а указатель - 8 байтов (64 бита).
intptr_t
переводит указатели на size_t
, С помощью этого безопасного метода вы можете написать кроссплатформенный совместимый код.
Например:
size_t ref(void *pointer) {
return (size_t)(intptr_t)pointer;
}