size_t против uintptr_t
Стандарт C гарантирует, что size_t
тип, который может содержать любой индекс массива Это означает, что по логике size_t
должен быть в состоянии держать любой тип указателя. Я читал на некоторых сайтах, которые я нашел на Google, что это законно и / или должно всегда работать:
void *v = malloc(10);
size_t s = (size_t) v;
Итак, в C99 стандарт ввел intptr_t
а также uintptr_t
типы, которые являются подписанными и неподписанными типами, гарантированно способными содержать указатели:
uintptr_t p = (size_t) v;
Так в чем же разница между использованием size_t
а также uintptr_t
? Оба без знака, и оба должны быть в состоянии содержать любой тип указателя, поэтому они кажутся функционально идентичными. Есть ли реальная веская причина для использования uintptr_t
(или еще лучше, void *
) а не size_t
кроме ясности? В непрозрачной структуре, где поле будет обрабатываться только внутренними функциями, есть ли причина не делать этого?
К тому же, ptrdiff_t
был подписанным типом, способным содержать различия указателей, и, следовательно, способным содержать практически любой указатель, так как он отличается от intptr_t
?
Разве все эти типы не обслуживают тривиально разные версии одной и той же функции? Если нет, то почему? Что я не могу сделать с одним из них, что я не могу сделать с другим? Если так, почему C99 добавил два по существу лишних типа к языку?
Я готов не обращать внимания на указатели функций, поскольку они не относятся к текущей проблеме, но не стесняйтесь упоминать их, так как у меня есть скрытое подозрение, что они будут центральными для "правильного" ответа.
8 ответов
size_t
тип, который может содержать любой индекс массива Это означает, что, по логике, size_t должен содержать любой указатель
Не обязательно! Вернемся к временам сегментированных 16-битных архитектур, например: массив может быть ограничен одним сегментом (поэтому 16-битный size_t
будет делать) НО вы можете иметь несколько сегментов (так что 32-битный intptr_t
Тип будет необходим, чтобы выбрать сегмент, а также смещение в нем). Я знаю, что эти вещи звучат странно в наши дни несегментированных архитектур с единой адресацией, но стандарт ДОЛЖЕН обслуживать более широкий спектр, чем "что нормально в 2009 году", вы знаете!-)
Что касается вашего заявления:
"Стандарт C гарантирует, что
size_t
тип, который может содержать любой индекс массива Это означает, что по логикеsize_t
должен быть в состоянии держать любой тип указателя."
Это на самом деле заблуждение (заблуждение из-за неправильных рассуждений)(а). Вы можете подумать, что последнее следует из первого, но на самом деле это не так.
Указатели и индексы массивов - это не одно и то же. Вполне правдоподобно предусмотреть соответствующую реализацию, которая ограничивает массивы 65536 элементами, но позволяет указателям обращаться к любому значению в массивном 128-битном адресном пространстве.
C99 утверждает, что верхний предел size_t
переменная определяется SIZE_MAX
и это может быть всего 65535 (см. C99 TR3, 7.18.3, без изменений в C11). Указатели были бы довольно ограничены, если бы они были ограничены этим диапазоном в современных системах.
На практике вы, вероятно, обнаружите, что ваше предположение верно, но это не потому, что стандарт гарантирует это. Потому что на самом деле это не гарантирует.
(а) Кстати, это не какая-то форма личной атаки, а просто заявление о том, почему ваши заявления ошибочны в контексте критического мышления. Например, следующие рассуждения также недействительны:
Все щенки милые. Эта штука милая. Поэтому эта вещь должна быть щенком.
Симпатичность или иное отношение к щенкам здесь не имеет отношения, все, что я утверждаю, это то, что два факта не приводят к выводу, потому что первые два предложения допускают существование милых вещей, которые не являются щенками.
Это похоже на ваше первое утверждение, не обязательно обязательное второе.
Я позволю всем остальным ответам выступать за рассуждения с ограничениями сегментов, экзотическими архитектурами и так далее.
Разве простой разницы в именах недостаточно, чтобы использовать правильный тип для правильной вещи?
Если вы храните размер, используйте size_t
, Если вы храните указатель, используйте intptr_t
, Человек, читающий ваш код, сразу узнает, что "ага, это размер чего-то, возможно, в байтах", и "о, вот значение указателя, по какой-то причине сохраняемое как целое число".
В противном случае, вы можете просто использовать unsigned long
(или, в эти здесь современные времена, unsigned long long
) За все. Размер - это еще не все, имена типов несут значение, которое полезно, поскольку помогает описать программу.
Возможно, что размер самого большого массива меньше, чем указатель. Подумайте о сегментированных архитектурах - указатели могут быть 32-разрядными, но один сегмент может быть способен адресовать только 64 КБ (например, старая архитектура 8086 реального режима).
Хотя они обычно не используются на настольных компьютерах, стандарт C предназначен для поддержки даже небольших специализированных архитектур. Например, все еще разрабатываются встроенные системы с 8- или 16-битными процессорами.
Я хотел бы представить (и это относится ко всем именам типов), что он лучше передает ваши намерения в коде.
Например, хотя unsigned short
а также wchar_t
одинакового размера на Windows (я думаю), используя wchar_t
вместо unsigned short
показывает намерение использовать его для хранения широкого символа, а не просто произвольного числа.
Оглядываясь назад и вперед, и вспоминая, что различные странные архитектуры были разбросаны по ландшафту, я почти уверен, что они пытались обернуть все существующие системы, а также предусмотреть все возможные будущие системы.
Так что, как бы все ни было улажено, нам пока нужно не так много типов.
Но даже в LP64, довольно распространенной парадигме, нам нужны size_t и ssize_t для интерфейса системных вызовов. Можно представить себе более ограниченную унаследованную или будущую систему, где использование полного 64-разрядного типа обходится дорого, и они могут захотеть использовать операции ввода-вывода объемом более 4 ГБ, но при этом все еще имеют 64-разрядные указатели.
Я думаю, вы должны задаться вопросом: что могло бы быть разработано, что может произойти в будущем. (Возможно, 128-битные указатели для распределенной системы через Интернет, но не более 64 бит в системном вызове, или, возможно, даже "устаревшее" 32-битное ограничение.:-) Представьте, что унаследованные системы могут получить новые компиляторы C..,
Кроме того, посмотрите на то, что существовало тогда. Помимо моделей памяти реального времени zillion 286, как насчет 60-битных мэйнфреймов CDC с 60-битным словом / 18-битным указателем? Как насчет серии Cray? Не берите в голову нормальные ILP64, LP64, LLP64. (Я всегда думал, что Майкрософт был претенциозным с LLP64, это должен был быть P64.) Я, конечно, могу представить комитет, пытающийся охватить все основы...
против.
В дополнение к другим хорошим ответам:
size_t
определяется в<stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <uchar.h>, <wchar.h>
. Он как минимум 16-битный.
определяется в<stdint.h>
. Это необязательно . Совместимая библиотека может не определить его, вероятно, потому, что не существует достаточно широкого целочисленного типа для кругового обхода.void*
-uintptr_t
-void *
.
Оба являются беззнаковыми целочисленными типами.
Примечание: дополнительный компаньонintptr_t
является целочисленным типом со знаком .
int main(){
int a[4]={0,1,5,3};
int a0 = a[0];
int a1 = *(a+1);
int a2 = *(2+a);
int a3 = 3[a];
return a2;
}
Подразумевается, что intptr_t всегда должен заменять size_t и наоборот.