Вычитание указателя и альтернатива

При работе с массивами стандартные алгоритмы (как на 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)