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 и наоборот.