Почему/когда использовать `intptr_t` для литья типов в C?

У меня есть вопрос относительно использования intptr_t vs. long int. Я заметил, что увеличение адресов памяти (например, с помощью арифметики ручного указателя) отличается типом данных. Например, приращение указателя char добавляет 1 к адресу памяти, тогда как приращение указателя int добавляет 4, 8 для double, 16 для длинного двойника и т.д....

Сначала я сделал что-то вроде этого:

char myChar, *pChar;
float myFloat, *pFloat;

pChar = &myChar;
pFloat = &myFloat;

printf( "pChar:  %d\n", ( int )pChar );
printf( "pFloat: %d\n", ( int )pFloat );

pChar++;
pFloat++;

printf( "and then after incrementing,:\n\n" );
printf( "pChar:  %d\n", (int)pChar );
printf( "pFloat:    %d\n", (int)pFloat );

который скомпилирован и выполнен просто отлично, но XCode дал мне предупреждения для моего typecasting: "Отправлено от указателя до целого разного размера".

После некоторых поисковых запросов и бинга (последнее еще слово?), я видел, как некоторые люди рекомендуют использовать intptr_t:

#include <stdint.h>

...

printf( "pChar:  %ld\n", ( intptr_t )pChar );
printf( "pFloat: %ld\n", ( intptr_t )pFloat );

который действительно устраняет ошибки. Итак, я подумал, что теперь я должен использовать intptr_t для указателей указателей... Но потом после некоторого фиджинга я обнаружил, что могу решить проблему, просто заменив int на long int:

printf( "pChar:  %ld\n", ( long int )pChar );
printf( "pFloat: %ld\n", ( long int )pFloat );

Итак, мой вопрос: почему intptr_t полезно, и когда он должен использоваться? В данном случае это кажется излишним. Очевидно, адреса памяти для myChar и myFloat были слишком большими, чтобы вписаться в int... поэтому приведение их в соответствие с long int решило проблему.

Разве иногда адреса памяти слишком большие для long int? Теперь, когда я думаю об этом, я думаю, что это возможно, если у вас есть > 4 ГБ ОЗУ, и в этом случае адреса памяти могут превышать 2 ^ 32 - 1 (максимальное значение для unsigned long ints...), но C было создано задолго до того, можно вообразить, не так ли? Или они были настолько предсказуемыми?

Спасибо!

Ответ 1

Здесь вещь: на некоторых платформах int - правильный размер, а на других long - правильный размер. Откуда вы знаете, какой из них вы должны использовать? Вы этого не сделаете. Одно может быть правильным, но стандарт не дает никаких гарантий относительно того, какой из них он будет (если это так). Таким образом, стандарт предоставляет тип, который определен как правильный размер, независимо от того, на какой платформе вы находитесь. Где раньше вам приходилось писать:

#ifdef PLATFORM_A
  typedef long intptr;
#else
  typedef int intptr;
#endif

Теперь вы просто пишете:

#include <stdint.h>

И он охватывает гораздо больше случаев. Представьте, что вы выделили фрагмент выше для каждой платформы, на которой работает ваш код.

Ответ 2

intptr_t - новое изобретение, созданное после представления 64-битных и даже 128-битных адресов памяти.

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

Потребовалось много времени, чтобы сгладить все ошибки с помощью таких программ, как Mozilla/Firefox, когда люди захотели скомпилировать их на 64-разрядной Linux.

Ответ 3

Во-первых, intptr_t предназначен только для указателей данных (а не для функций) и не гарантируется существование.

Тогда нет, вы не должны использовать его для печати. Для этого используется %p. Вам просто нужно направить указатель на (void*) и там вы идете.

Это также не полезно для арифметики/доступа к отдельным байтам. Вместо этого используйте (unsigned char*).

intptr_t действительно для редких случаев, что вы должны интерпретировать указатели как целые числа (которых они действительно не являются). Не делайте этого, если не хотите.

Ответ 4

Вы можете сделать свою жизнь проще, используя спецификатор преобразования p:

printf("%p\n", (void *)foo);

Кроме того, переносимым способом печати переменной типа (u)intptr_t является использование макросов PRI*PTR из inttypes.h; следующее эквивалентно использованию p на моей платформе (32-разрядной версии):

printf("%08" PRIxPTR "\n", (uintptr_t)(void *)foo);

Приводы для void * необходимы для полной переносимости, но могут быть опущены на платформах с равномерными представлениями указателей.