Вычитание указателя и альтернатива
При работе с массивами стандартные алгоритмы (как на C, так и на С++) часто возвращают указатели на элементы. Иногда бывает удобно иметь индекс элемента, возможно, для индексации в другой массив, и я обычно получаю это путем вычитания начала массива из указателя:
int arr[100];
int *addressICareAbout = f(arr, 100);
size_t index = addressICareAbout - arr;
Это всегда казалось простым и эффективным. Однако недавно мне было указано, что вычитание указателя фактически возвращает a ptrdiff_t
и что в принципе могут возникнуть проблемы, если "index
" не вписывается в ptrdiff_t
. Я действительно не верил, что любая реализация будет достаточно извращенной, чтобы позволить создать такой большой arr (и тем самым вызвать такие проблемы), но принятый ответ здесь допускает что это возможно, и я не нашел доказательств, чтобы предлагать иное. Поэтому я смирился с тем, что это так (если кто-то не убедит меня иначе) и будет осторожным в будущем. Этот ответ предлагает довольно запутанный метод "безопасного" получения индекса; действительно ли ничего лучше?
Тем не менее, я смущен о возможном обходном пути в С++. Мы имеем std::distance
, но std::distance(arr, addressICareAbout)
гарантированно четко определены? С одной стороны, (указатель на первый элемент) arr
может быть увеличен до достижения addressICareAbout
(справа?), Но с другой стороны std::distance
должен вернуть ptrdiff_t
. Итераторы для стандартных контейнеров могут (предположительно) иметь одинаковые проблемы.
Ответы
Ответ 1
Очень маловероятно, что у вас когда-либо будет два указателя на тот же массив, где разница не вписывается в ptrdiff_t.
В 64-битных реализациях ptrdiff_t подписан 64-разрядной версией, поэтому вам понадобится массив из 8 миллиардов гигабайт. В 32-битных реализациях обычно ваше общее адресное пространство ограничено 3 ГБ, 3 1/4 ГБ, если вам повезет (это адресное пространство, а не ОЗУ, которое считается), поэтому вам понадобится массив из более чем 2 ГБ, который не оставляет ничего для чего-либо еще. И вполне возможно, что malloc откажется выделить массив такого размера в первую очередь. Ваше мнение конечно.
В то время как std:: distance имеет преимущества, я подозреваю, что он имеет ту же теоретическую проблему, что и ptrdiff_t, поскольку расстояния могут быть положительными и отрицательными, и, вероятно, это не 64-разрядный тип при 32-битной реализации.
Обратите внимание, что если вы можете выделить массив 3 ГБ в 32-битной реализации, и у вас есть два int * для первого и последнего элементов этого массива, я не удивлюсь, если разность указателей вычисляется неправильно даже хотя результат вписывается в ptrdiff_t.
Ответ 2
Возможное обходное решение:
Переместите оба указателя на uintptr_t
, вычитайте и разделите на sizeof (T)
самостоятельно. Это не совсем переносимо, но оно никогда не должно быть undefined, и большинство систем определяют преобразование указателя integer ↔ таким образом, что это делает работу.
Действительно переносимое (но менее эффективное) обходное решение:
Используйте альтернативный базовый указатель. Если массив больше, чем 2<<30
элементов, то вы можете законно вычислить step = p1 + (2<<30)
, использовать реляционные операторы, чтобы увидеть, есть ли p2 > step
, и если да, подсчитайте смещение как (2u << 30) + uintptr_t(distance(step, p2))
Обратите внимание, что для рекурсивного вызова может потребоваться другое шаг.
Ответ 3
Я не нашел доказательств, чтобы предлагать обратное.
К счастью, здесь есть доказательства: http://en.cppreference.com/w/cpp/types/size_t
std::size_t
может хранить максимальный размер теоретически возможного объекта любого типа (включая массив). Тип, размер которого не может быть представлен std::size_t
, плохо сформирован (поскольку С++ 14)