size_t против uintptr_t


246

Стандарт 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(или, что еще лучше, a void *), а не a size_t, кроме ясности? В непрозрачной структуре, где поле будет обрабатываться только внутренними функциями, есть ли причина не делать этого?

Точно так же ptrdiff_tбыл подписанный тип, способный содержать различия в указателях и, следовательно, способный удерживать практически любой указатель, так чем он отличается от intptr_t?

Разве все эти типы не обслуживают тривиально разные версии одной и той же функции? Если нет, то почему? Что я не могу сделать с одним из них, что я не могу сделать с другим? Если так, то почему C99 добавил в язык два по существу лишних типа?

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

Ответы:


236

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

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


6
Это, наряду с многочисленными другими , которые прыгали к такому же выводу, объясняет разницу между size_tи , uintptr_tкроме того, что о ptrdiff_tи intptr_t- не оба из них будет иметь возможность хранить один и тот же диапазон значений почти на любой платформе? Почему существуют целочисленные типы как со знаком, так и без знака, особенно если они ptrdiff_tуже предназначены для целочисленного типа со значением со знаком.
Крис Латс

8
Ключевая фраза " почти на любой платформе", @Chris. Реализация свободна ограничить указатели диапазоном 0xf000-0xffff - для этого требуется 16-битный intptr_t, но только 12/13-битный ptrdiff_t.
paxdiablo

29
@Chris, только для указателей внутри одного и того же массива четко определена их разница. Таким образом, на точно таких же сегментированных 16-битных архитектурах (массив должен находиться внутри одного сегмента, но два разных массива могут находиться в разных сегментах), указатели должны быть 4 байта, но разница указателей может составлять 2 байта!
Алекс Мартелли

6
@AlexMartelli: за исключением того, что различия в указателях могут быть положительными или отрицательными. Стандарт size_tдолжен быть не менее 16 бит, но ptrdiff_tне менее 17 бит (что на практике означает, что он, вероятно, будет не менее 32 бит).
Кит Томпсон,

3
Неважно, сегментированные архитектуры, как насчет современной архитектуры, такой как x86-64? Ранние реализации этой архитектуры дают вам только 48-битное адресуемое пространство, но сами указатели представляют собой 64-битный тип данных. Самый большой непрерывный блок памяти, который вы могли бы разумно адресовать, был бы 48-битным, поэтому я должен представить, SIZE_MAXчто он не должен быть 2 ** 64. Имейте в виду, что используется плоская адресация; Сегментация не требуется для того, чтобы иметь несоответствие между SIZE_MAXдиапазоном указателя данных.
Andon M. Coleman

89

По поводу вашего заявления:

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

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

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

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

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


(а) Кстати, это не какая-то личная атака, а просто заявление о том, почему ваши высказывания ошибочны в контексте критического мышления. Например, следующие рассуждения также недействительны:

Все щенки милые. Эта штука милая. Поэтому эта вещь должна быть щенком.

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

Это похоже на ваше первое утверждение, не обязательно обязательное второе.


Вместо того, чтобы перепечатывать то, что я сказал в комментариях к Алексу Мартелли, я просто скажу спасибо за разъяснения, но повторю вторую половину моего вопроса ( часть ptrdiff_tпротив intptr_t).
Крис Латс

5
@Ivan, как и в большинстве случаев общения, необходимо общее понимание определенных базовых вопросов. Если вы видите этот ответ как «высмеивание», уверяю вас, это неправильное понимание моего намерения. Предполагая, что вы ссылаетесь на мой комментарий «логической ошибки» (я не вижу никакой другой возможности), это означало фактическое утверждение, а не какое-то утверждение, сделанное за счет ФП. Если вы хотите предложить какое-то конкретное улучшение, чтобы минимизировать возможность недопонимания (а не просто жалобу общего характера), я был бы рад рассмотреть.
paxdiablo

1
@ivan_pozdeev - это отвратительная и радикальная пара правок, и я не вижу доказательств того, что Паксдиабло «подшучивал» над кем-то. Если бы я был OP, я бы откатил это обратно ...
ex nihilo

1
@ Иван, был не очень доволен предложенными вами правками, откатился, а также попытался удалить любое непреднамеренное нарушение. Если у вас есть другие предложения, я бы предложил начать чат, чтобы мы могли обсудить.
paxdiablo

1
@paxdiablo хорошо, я думаю, "это на самом деле заблуждение", это меньше покровительствует.
ivan_pozdeev

36

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

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

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

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


Я согласен, но я рассматривал что-то вроде хитрости / уловки (которую я бы, конечно, четко задокументировал), связанной с сохранением типа указателя в size_tполе.
Крис Латс

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

12

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

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


Но вы можете индексировать указатели точно так же, как массивы, поэтому size_tтакже должны быть в состоянии справиться с этим? Или динамические массивы в некотором удаленном сегменте все еще будут ограничены индексированием в пределах их сегмента?
Крис Латс

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

7
Прочитайте en.wikipedia.org/wiki/C_memory_model#Memory_segmentation и оплакивайте программистов MS-DOS, которые умерли, чтобы мы могли быть свободными.
Justicle

Хуже всего было то, что функция stdlib не заботилась об огромном ключевом слове. 16bit МС-С для всех strфункций и Борланд даже для memфункций ( memset, memcpy, memmove). Это означало, что при переполнении смещения вы могли перезаписывать часть памяти, что было интересно отлаживать на нашей встроенной платформе.
Патрик Шлютер

@Justicle: Сегментированная архитектура 8086 не очень хорошо поддерживается в C, но я не знаю другой архитектуры, которая была бы более эффективной в тех случаях, когда адресного пространства 1 МБ достаточно, а 64 Кбайт было бы недостаточно. Некоторые современные JVM фактически используют адресацию, очень похожую на реальный режим x86, используя смещение 32-битных ссылок на объекты влево на 3 бита для генерации базовых адресов объектов в адресном пространстве 32 ГБ.
суперкат

5

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

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


Но здесь есть разница - в моей системе wchar_tона намного больше, чем unsigned shortиспользование одной для другой, будет ошибочным и создаст серьезную (и современную) проблему переносимости, в то время как проблемы переносимости между size_tи, uintptr_tкажется, лежат в отдаленных странах. что-то 1980-го (случайный удар в темноте на свидание, там)
Крис Латс

Туше! Но опять же, size_tи uintptr_tдо сих пор подразумевается использование в их именах.
Dreamlax

Они делают, и я хотел знать, была ли мотивация для этого сверх ясности. И оказывается, что есть.
Крис Латс

3

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

Так что, как бы все ни было улажено, нам пока нужно не так много типов.

Но даже в LP64, довольно распространенной парадигме, нам нужны size_t и ssize_t для интерфейса системных вызовов. Можно представить себе более ограниченную унаследованную или будущую систему, где использование полного 64-разрядного типа обходится дорого, и они могут захотеть использовать операции ввода-вывода объемом более 4 ГБ, но при этом все еще имеют 64-разрядные указатели.

Я думаю, вы должны задаться вопросом: что могло бы быть разработано, что может произойти в будущем. (Возможно, 128-битные указатели для распределенной системы через Интернет, но не более 64 бит в системном вызове, или, возможно, даже «устаревшее» 32-битное ограничение. :-) Представьте, что унаследованные системы могут получить новые компиляторы C .. ,

Кроме того, посмотрите на то, что существовало тогда. Помимо моделей памяти реального времени zillion 286, как насчет мэйнфреймов CDC с 60-битным словом / 18-битным указателем? Как насчет серии Cray? Не берите в голову нормальные ILP64, LP64, LLP64. (Я всегда думал, что Microsoft - это претенциозно с LLP64, это должен был быть P64.) Я, конечно, могу представить комитет, пытающийся охватить все основы ...


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


10
Все это показывает особую синтаксическую причуду C. Индексация массива определяется в терминах x [y], эквивалентных * (x + y), и поскольку a + 3 и 3 + a идентичны по типу и значению, вы можете используйте 3 [a] или [3].
Фред Нурк
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.