size_t против uintptr_t

231

Стандарт 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 добавили два языка, которые были бы лишними к языку?

Я готов игнорировать указатели на функции, поскольку они не применяются к текущей проблеме, но не стесняйтесь упоминать их, поскольку у меня есть скрытое подозрение, что они будут иметь центральное значение для "правильного" ответа.

Теги:
pointers
size-t

7 ответов

208
Лучший ответ

size_t - это тип, который может содержать любой индекс массива. Это означает, что логически size_t должен иметь возможность удерживать любой тип указателя

Не обязательно! Вернитесь к дням сегментированных 16-битных архитектур, например: массив может быть ограничен одним сегментом (так что будет выполняться 16-разрядный size_t), но у вас может быть несколько сегментов (поэтому понадобится 32-разрядный тип intptr_t для выбора сегмента, а также смещения внутри него). Я знаю, что эти вещи кажутся странными в эти дни единообразно адресуемых несегментированных архитектур, но стандарт ДОЛЖЕН обслуживать более широкий спектр, чем "что нормальное в 2009 году", вы знаете! -)

  • 6
    Это, наряду со многими другими, которые пришли к одному и тому же выводу, объясняет разницу между size_t и uintptr_t но как насчет ptrdiff_t и intptr_t - не смогут ли оба из них хранить одинаковый диапазон значений практически на любой платформе? Почему существуют целочисленные типы как со ptrdiff_t , так и без знака, особенно если ptrdiff_t уже служит для целочисленного типа со знаком как размер указателя.
  • 8
    Ключевая фраза " почти на любой платформе", @Chris. Реализация свободна ограничить указатели диапазоном 0xf000-0xffff - для этого требуется 16-битный intptr_t, но только 12/13-битный ptrdiff_t.
Показать ещё 6 комментариев
77

Относительно вашего утверждения:

"Стандарт C гарантирует, что size_t является типом, который может содержать любой индекс массива. Это означает, что логически size_t должен содержать любой тип указателя."

Это называется ошибкой, заблуждением, вызванным неправильной аргументацией. Вы можете подумать, что последнее следует из первого, но это не обязательно так.

Указатели и индексы массивов - это не одно и то же. Весьма правдоподобно предусмотреть соответствующую реализацию, которая ограничивает массивы до 65536 элементов, но позволяет указателям обращаться к любому значению в массивное 128-битное адресное пространство.

C99 утверждает, что верхний предел переменной size_t определяется SIZE_MAX, и это может быть всего лишь 65535 (см. C99 TR3, 7.18.3, без изменений в C11). Указатели были бы довольно ограниченными, если бы они были ограничены этим диапазоном в современных системах.

На практике вы, вероятно, обнаружите, что ваше предположение имеет место, но это не потому, что стандарт гарантирует это. Потому что это на самом деле не гарантирует этого.

  • 0
    Вместо того, чтобы перепечатывать то, что я сказал в комментариях к Алексу Мартелли, я просто скажу спасибо за разъяснения, но повторю вторую половину моего вопроса (часть ptrdiff_t vs. intptr_t ).
  • 0
    Это разъяснение помогло мне. Спасибо!
Показать ещё 6 комментариев
34

Я дам всем остальным ответы на вопрос о рассуждениях с ограничениями сегмента, экзотической архитектурой и т.д.

Разве простое различие в именах не достаточно, чтобы использовать правильный тип для правильной вещи?

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

В противном случае вы можете просто использовать unsigned long (или, в данном случае, в наше время, unsigned long long) для всего. Размер не все, имена типов несут смысл, который полезен, поскольку он помогает описать программу.

  • 0
    Я согласен, но я рассматривал что-то вроде хитрости / уловки (которую я бы, конечно, четко задокументировал), включая сохранение типа указателя в поле size_t .
  • 0
    @MarkAdler Standard не требует, чтобы указатели были представимы как целые числа: любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа. Таким образом, только void* , intptr_t и uintptr_t гарантированно могут представлять любой указатель на данные.
13

Возможно, размер самого большого массива меньше, чем указатель. Подумайте о сегментированных архитектурах - указатели могут быть 32-битными, но один сегмент может иметь возможность адресовать только 64 КБ (например, старую архитектуру реального режима 8086).

Хотя они больше не используются на настольных компьютерах, стандарт C предназначен для поддержки даже небольших специализированных архитектур. Есть еще встроенные системы, которые разрабатываются с 8 или 16-разрядными процессорами, например.

  • 0
    Но вы можете индексировать указатели так же, как массивы, так должен ли size_t справиться с этим? Или динамические массивы в некотором отдаленном сегменте все еще будут ограничены индексированием в пределах их сегмента?
  • 0
    Указатели индексирования технически поддерживаются только для размера массива, на который они указывают - поэтому, если размер массива ограничен 64 КБ, это все, что необходимо для поддержки арифметики указателей. Однако компиляторы MS-DOS действительно поддерживали «огромную» модель памяти, где манипулировали дальними указателями (32-разрядными сегментированными указателями), чтобы они могли обращаться ко всей памяти как к одному массиву, но арифметика, сделанная для указателей за кулисами, была довольно некрасиво - когда смещение увеличилось после значения 16 (или чего-то еще), смещение было перенесено обратно на 0, а часть сегмента была увеличена.
Показать ещё 5 комментариев
5

Я бы предположил (и это касается всех имен типов), что он лучше передает ваши намерения в коде.

Например, хотя unsigned short и wchar_t имеют одинаковый размер в Windows (я думаю), использование wchar_t вместо unsigned short показывает намерение использовать его для хранения широкого символа, чем просто произвольное число.

  • 0
    Но здесь есть разница - в моей системе wchar_t намного больше, чем unsigned short поэтому использование одного для другого будет ошибочным и создаст серьезную (и современную) проблему переносимости, тогда как проблемы с переносимостью между size_t и uintptr_t похоже, лежат в отдаленные земли 1980-х годов (случайный удар в темное время суток)
  • 0
    Туше! Но опять же, size_t и uintptr_t все еще подразумевают использование в своих именах.
Показать ещё 1 комментарий
2

Глядя как назад, так и вперед и вспоминая, что различные ландшафтные архитектуры были разбросаны по ландшафту, я уверен, что они пытались обернуть все существующие системы, а также обеспечить все возможные будущие системы.

Итак, так, как все наладилось, нам пока не нужно было так много типов.

Но даже в 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 и наоборот.

  • 9
    Все это показывает особую синтаксическую причуду C. Индексация массива определяется в терминах x [y], эквивалентных * (x + y), и поскольку a + 3 и 3 + a идентичны по типу и значению, вы можете используйте 3 [a] или [3].

Ещё вопросы

Сообщество Overcoder
Наверх
Меню