Size_t vs. uintptr_t
Стандарт C гарантирует, что size_t
- это тип, который может содержать любой индекс массива. Это означает, что логически size_t
должен содержать любой тип указателя. Я читал на некоторых сайтах, которые я нашел в Googles, что это законно и/или всегда должно работать:
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
(или еще лучше, a void *
), а не size_t
, кроме ясности? В непрозрачной структуре, где поле будет обрабатываться только внутренними функциями, есть ли причина не делать этого?
Точно так же ptrdiff_t
был подписанным типом, способным удерживать различия указателя и, следовательно, способным удерживать большинство указателей, так как он отличается от intptr_t
?
Разве не все эти типы в основном обслуживают тривиально разные версии одной и той же функции? Если нет, то почему? Что я не могу сделать с одним из них, с которым я не могу справиться? Если да, то почему C99 добавили два языка, которые были бы лишними к языку?
Я готов игнорировать указатели на функции, поскольку они не применяются к текущей проблеме, но не стесняйтесь упоминать их, поскольку у меня есть скрытое подозрение, что они будут иметь центральное значение для "правильного" ответа.
Ответы
Ответ 1
size_t
- это тип, который может содержать любой индекс массива. Это означает, что логически size_t должен иметь возможность удерживать любой тип указателя
Не обязательно! Вернитесь к дням сегментированных 16-битных архитектур, например: массив может быть ограничен одним сегментом (так что будет выполняться 16-разрядный size_t
), но у вас может быть несколько сегментов (поэтому понадобится 32-разрядный тип intptr_t
для выбора сегмента, а также смещения внутри него). Я знаю, что эти вещи кажутся странными в эти дни единообразно адресуемых несегментированных архитектур, но стандарт ДОЛЖЕН обслуживать более широкий спектр, чем "что нормальное в 2009 году", вы знаете! -)
Ответ 2
Относительно вашего утверждения:
"Стандарт C гарантирует, что size_t
является типом, который может содержать любой индекс массива. Это означает, что логически size_t
должен содержать любой тип указателя."
Это называется ошибкой, заблуждением, вызванным неправильной аргументацией. Вы можете подумать, что последнее следует из первого, но это не обязательно так.
Указатели и индексы массивов - это не одно и то же. Весьма правдоподобно предусмотреть соответствующую реализацию, которая ограничивает массивы до 65536 элементов, но позволяет указателям обращаться к любому значению в массивное 128-битное адресное пространство.
C99 утверждает, что верхний предел переменной size_t
определяется SIZE_MAX
, и это может быть всего лишь 65535 (см. C99 TR3, 7.18.3, без изменений в C11). Указатели были бы довольно ограниченными, если бы они были ограничены этим диапазоном в современных системах.
На практике вы, вероятно, обнаружите, что ваше предположение имеет место, но это не потому, что стандарт гарантирует это. Потому что это на самом деле не гарантирует этого.
Ответ 3
Я дам всем остальным ответы на вопрос о рассуждениях с ограничениями сегмента, экзотической архитектурой и т.д.
Разве простое различие в именах не достаточно, чтобы использовать правильный тип для правильной вещи?
Если вы сохраняете размер, используйте size_t
. Если вы храните указатель, используйте intptr_t
. Человек, читающий ваш код, сразу узнает, что "ага, это размер чего-то, возможно, в байтах" и "о, здесь значение указателя, которое хранится как целое по какой-либо причине".
В противном случае вы можете просто использовать unsigned long
(или, в данном случае, в наше время, unsigned long long
) для всего. Размер не все, имена типов несут смысл, который полезен, поскольку он помогает описать программу.
Ответ 4
Возможно, размер самого большого массива меньше, чем указатель. Подумайте о сегментированных архитектурах - указатели могут быть 32-битными, но один сегмент может иметь возможность адресовать только 64 КБ (например, старую архитектуру реального режима 8086).
Хотя они больше не используются на настольных компьютерах, стандарт C предназначен для поддержки даже небольших специализированных архитектур. Есть еще встроенные системы, которые разрабатываются с 8 или 16-разрядными процессорами, например.
Ответ 5
Я бы предположил (и это касается всех имен типов), что он лучше передает ваши намерения в коде.
Например, хотя unsigned short
и wchar_t
имеют одинаковый размер в Windows (я думаю), использование wchar_t
вместо unsigned short
показывает намерение использовать его для хранения широкого символа, чем просто произвольное число.
Ответ 6
Глядя как назад, так и вперед и вспоминая, что различные ландшафтные архитектуры были разбросаны по ландшафту, я уверен, что они пытались обернуть все существующие системы, а также обеспечить все возможные будущие системы.
Итак, так, как все наладилось, нам пока не нужно было так много типов.
Но даже в LP64, довольно распространенной парадигме, нам нужны были size_t и ssize_t для интерфейса системного вызова. Можно представить себе более ограниченную устаревшую или будущую систему, где использование полного 64-битного типа стоит дорого, и они могут захотеть использовать операции ввода-вывода размером более 4 ГБ, но все еще имеют 64-битные указатели.
Я думаю, вам нужно задаться вопросом: что могло бы быть развито, что может произойти в будущем. (Возможно, 128-битные широкополосные указатели с распределенной системой, но не более 64 бит в системном вызове или, возможно, даже "устаревшее" 32-битное ограничение.:-) Изображение, что унаследованные системы могут получить новые компиляторы C..
Кроме того, посмотрите, что было тогда. Помимо моделей 286 реального времени в режиме реального времени, как насчет 60-битных мейнфреймов с метрическими/18-битными указателями CDC? Как насчет серии Cray? Не обращайте внимания на обычные ILP64, LP64, LLP64. (Я всегда считал, что Microsoft сдержанно относится к LLP64, это должен был быть P64.) Я, конечно же, могу представить, что комитет пытается охватить все базы...
Ответ 7
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 и наоборот.