Подтвердить что ты не робот

Почему ~ size_t (0) (== 0xFFFFFFFF в большинстве 32-разрядных систем) не является допустимым индексом массива?

Цитата из этого блога:

http://www.codesynthesis.com/~boris/blog/2008/10/13/writing-64-bit-safe-code/

Это работает, потому что действительный индекс памяти может быть только в диапазоне [0, ~ size_t (0) -1]. Тот же подход, например, используется в std::string.

Итак, почему ~size_t(0) (это обычно должно равняться 0xFFFFFFFF в 32-битных системах), а не действительный индекс массива? Я предполагаю, что если у вас есть 32 бита, вы должны иметь возможность ссылаться на весь диапазон [0, 0xFFFFFFFF], no?

4b9b3361

Ответ 1

ВАЖНОЕ ПРИМЕЧАНИЕ: Термин "индекс памяти" неоднозначен и запутан. Связанная статья относится строго к индексам массива, а не к адресам в памяти. Для size_t вполне справедливо быть неспособным представлять все адреса памяти, поэтому у нас есть тип intptr_t на C99. Конечно, это не относится к вашей рабочей станции, что, несомненно, имеет простую архитектуру типа фон Неймана. (С тех пор этот вопрос был отредактирован для удаления ссылок на "индексы памяти".)

Стандарт C гарантирует, что size_t может содержать размер любого массива. Однако для любого массива a[N] стандарт гарантирует, что a + N должен быть допустимым указателем и сравнивать не равным любому указателю на элемент a.

Следовательно, size_t должен иметь возможность представлять хотя бы одно значение, большее любого возможного индекса массива. Поскольку ~(size_t)0 гарантированно является максимальным значением size_t, это хороший выбор дозорного для индексов массива.

Обсуждение:

  • Почему ~(size_t)0 гарантированно является максимальным? Поскольку стандарт явно говорит так: из § 6.5.3.3: "Если продвигаемый тип является неподписанным типом, выражение ~E эквивалентно максимальному значению, представленному в этом тире минус E". Обратите внимание, что (size_t)-1 гарантированно также будет максимальным по правилам преобразования от подписанных к неподписанным типам. К сожалению, не всегда легко найти определение для SIZE_MAX на вашей платформе, поэтому (size_t)-1 и ~(size_t)0 являются предпочтительными. (Обратите внимание, что это уже не так, если int может представлять SIZE_MAX... но это не то, что произойдет в реальной системе.)

  • Каков размер массива с индексом от 0 до ~ 0? Такой массив не может существовать в соответствии со стандартом C, аргументом, изложенным в верхней части этого сообщения.

  • Если вы malloc(-1), результирующая область памяти должна начинаться с нуля (FALSE). Есть много действительно странных случаев, которые стандарт позволяет, но на практике это не встречается. Например, представьте себе систему, где (uintptr_t)-1 > (size_t)-1. Стандарт C формулируется точно так, как это происходит, потому что он не просто запускается на вашем ПК, он работает на причудливых небольших DSP с архитектурой Гарварда, и он работает на архаичных системах с византийскими схемами сегментирования памяти. Существуют также некоторые системы, представляющие исторический интерес, где указатели NULL не имеют того же представления, что и 0.

Ответ 2

Интуиция такова:

x = malloc(~size_t(0));    // the most you can allocate
x[~size_t(0) -1];          // the highest valid index

Ответ 3

Ну, как @Apprentice Queue правильно отметили в своем ответе, так как размер самого большого непрерывного объекта в C или С++-программе ограничен SIZE_MAX (так же, как (size_t) -1, то же, что и ~size_t(0)), максимальный индекс вам нужно будет индексировать байты этого объекта SIZE_MAX - 1. Но в то же время, как @Dietrich Epp правильно отмечает в своем ответе, C и С++ разрешают арифметику адреса одного элемента за пределами массива, что делает SIZE_MAX допустимым индексом, если не для доступа к элементам массива, тогда на наименее для арифметики указателя. Таким образом, формально говоря SIZE_MAX является допустимым индексом, хотя он не может стоять за существующий элемент массива.

Однако вся идея использования size_t как типа, который позволяет "индексировать всю память", действительна только в пределах некоторой конкретной платформы, где size_t действительно оказывается достаточным для индексирования памяти ( К этой категории относятся платформы с плоской памятью, такие как Win32, Win64, Linux). В действительности, с общей точки зрения, тип size_t недостаточен для индексирования памяти. Тип size_t гарантирован только для того, чтобы индексировать байты в одном непрерывном объекте в программе C или С++. Ни C, ни С++ не гарантируют поддержку непрерывных объектов, которые охватывают все адресное пространство. Другими словами, тип size_t, как гарантируется, будет достаточным для индексации любого явно объявленного объекта массива в программе C/С++, но, как правило, этого недостаточно для подсчета узлов в связанном списке. Тем не менее, в предположении, что на некоторой конкретной платформе диапазон size_t покрывает всю память, значение (size_t) -1 выглядит как хороший выбор "зарезервированного" значения, так как этот индекс может стоять только за последний байт в массив байтов, охватывающий все адресное пространство. Очевидно, что никто никогда не будет нуждаться в этом индексе на практике для фактической индексации.

Тем не менее, если вас действительно интересует формально соответствующий тип, который может индексировать всю память (и, следовательно, способен хранить количество элементов в любом контейнере в памяти), это будет uintptr_t, а не size_t. Автор этого блога, похоже, понимает общую проблему здесь, так как он отмечает, что size_t не подходит для индексирования файлов (т.е. Для хранения размеров или смещений файлов). Тем не менее, было бы неплохо заметить, что для тех же самых причин тип size_t не подходит для индексирования памяти. Тип size_t на самом деле не связан с ОЗУ или адресным пространством процесса, вопреки тому, что автор утверждает в своей записи в блоге. Тип uintptr_t связан с адресным пространством процесса, но не size_t. Тот факт, что size_t достаточно на платформах, которые он упоминает, является не чем иным, как специфическим свойством этих платформ.

Ответ 4

Да; До сих пор я прочитал все ответы. Теперь я добавлю простой пример с двумя значительными битами. Максимальное представляемое 2-битное значение без знака равно 3, что означает, что мы можем выделить в лучшем случае 3 единицы хранения.

Индексы к единицам: [0, 1, 2] - обратите внимание, что максимальное значение 3 не находится в пределах диапазона адресов, потому что невозможно выделить хранилище с большим количеством всего с двумя битами.

Основной причиной этого является то, что индексирование в C и С++ начинается с нуля. Индексы "отключены на единицу", потому что размер 1 перекрывает смещение 0. Это приводит к тому, что индексы с таким же количеством бит достигают одного элемента, кроме размера.

Это версия ответа ELI-5.

Практические примеры:

буфер

char [4];//ОШИБКА: 4 не представляется с 2 битами

uint32_t buffer [1];//HACK: мы выделили 4 символа.. теперь что?

Ответ 5

Ответ Дитрих Эпп, конечно, совершенно правильный. Тем не менее, я хочу добавить к нему техническую причину: просто невозможно выделить массив настолько большой, чтобы ~size_t(0) мог его индексировать.

Дело в том, что в вашем адресном пространстве в каждый момент времени есть как минимум несколько байтов кода, поэтому вы просто не имеете всего виртуального адресного пространства в вашем распоряжении для сопоставления массива. (И я даже не упоминал о распределении для пространства стека и нулевой страницы!) Понимаете, единственное место, где массив, который может быть проиндексирован с помощью ~size_t(0), может быть сопоставлен, равен нулю, и он будет распространяться на каждый байт виртуальной памяти, так что последний байт может быть проиндексирован. Это невозможно сделать.

Также рассмотрите это: ваш ЦП не заботится о знаке при добавлении чисел. Для CPU добавление ~size_t(0) к значению совпадает с уменьшением этого значения на единицу. И уменьшение любого указателя на один указывает на то, что оно находится за пределами выделенного диапазона памяти, если только это не указатель null, который не может указывать на допустимый диапазон памяти...

Ограничение языкового стандарта, что распределения не могут быть больше ~size_t(0), намного слабее, чем эти технические ограничения.