Проверка, указывает ли указатель в массиве
Могу ли я проверить, указывает ли данный указатель на объект в массиве, заданном его границами?
template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
return begin <= p && p < end;
}
Или сравнение указателей вызывает неопределенное поведение, если p
указывает за пределы массива? В таком случае, как мне решить проблему? Это работает с пустыми указателями? Или это невозможно решить?
6 ответов
Хотя сравнение допустимо только для указателей в массиве и "один за концом", допустимо использовать набор или карту с указателем в качестве ключа, который использует std::less<T*>
В 1996 году было много дискуссий об этом на comp.std.C++.
Единственный правильный способ сделать это - такой подход.
template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
for (; begin != end; ++begin)
{
if (p == begin)
return true;
}
return false;
}
Совершенно очевидно, что это не работает, если T == void
, Я не уверен, что два void*
технически определить диапазон или нет. Конечно, если у вас было Derived[n]
было бы неправильно говорить, что (Base*)Derived, (Base*)(Derived + n)
определили допустимый диапазон, поэтому я не вижу его действительным для определения диапазона с чем-либо, кроме указателя на фактический тип элемента массива.
Приведенный ниже метод дает сбой, потому что неизвестно, что <
возвращает, если два операнда не указывают на элементы одного и того же объекта или элементы одного и того же массива. (5.9 [expr.rel] / 2)
template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
return !(p < begin) && (p < end);
}
Метод ниже не работает, потому что это также не определено, что std::less<T*>::operator()
возвращает, если два операнда не указывают на элементы одного и того же объекта или элементы одного и того же массива.
Это правда, что std::less
должен быть специализирован для любого типа указателя, чтобы получить общий порядок, если встроенный в <
нет, но это полезно только для таких целей, как предоставление ключа для set
или же map
, Не гарантируется, что общий порядок не будет чередовать отдельные массивы или объекты вместе.
Например, в архитектуре с сегментированной памятью смещение объекта может использоваться для <
и в качестве наиболее значимого дифференциатора для std::less<T*>
с индексом сегмента, используемого для разрыва связей. В такой системе элемент одного массива может быть упорядочен между границами второго отдельного массива.
template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
return !(std::less<T*>()(p, begin)) && (std::less<T*>()(p, end));
}
Прямо из документации MSDN:
Два указателя разных типов нельзя сравнивать, если:
- Один тип является типом класса, производным от другого типа.
- По крайней мере, один из указателей явно преобразуется (приведен) в тип void *. (Другой указатель неявно преобразуется в тип void * для преобразования.)
Так что void*
можно сравнить с чем-либо еще (включая другое void*
). Но даст ли сравнение значимые результаты?
Если два указателя указывают на элементы одного и того же массива или один элемент за концом массива, указатель на объект с более высоким индексом сравнивается выше. Сравнение указателей гарантировано действительным только в том случае, если указатели ссылаются на объекты в одном и том же массиве или на местоположение, расположенное за концом массива.
Похоже, нет. Если вы еще не знаете, что сравниваете элементы внутри массива (или просто проходите мимо него), то сравнение не обязательно будет иметь смысл.
Однако есть решение: STL обеспечивает std::less<>
а также std::greater<>
, который будет работать с любым типом указателя и будет давать правильные результаты во всех случаях:
if (std::less<T*>()(p, begin)) {
// p is out of bounds
}
Обновить:
Ответ на этот вопрос дает то же предположение (std::less
), а также цитирует стандарт.
Стандарт C++ не определяет, что происходит, когда вы сравниваете указатели с объектами, которые не находятся в одном и том же массиве, следовательно, с неопределенным поведением. Однако стандарт C++ - не единственный стандарт, которому должна соответствовать ваша платформа. Другие стандарты, такие как POSIX, определяют вещи, которые стандарт C++ оставляет как неопределенное поведение. На платформах с виртуальным адресным пространством, таких как Linux и Win32/64, вы можете сравнивать любые указатели, не вызывая неопределенного поведения.
Сравнение типов указателей не обязательно приводит к общему порядку. std::less/std:: большее_equal сделать, однако. Так...
template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
return std::greater_equal<T*>()(p, begin) && std::less<T*>()(p, end);
}
буду работать.
Не могли бы вы сделать это с std::distance
ваша проблема сводится к следующему:
return distance(begin, p) >= 0 && distance(begin, p) < distance(begin, end);
Учитывая, что этот итератор произвольного доступа (указатель) передается, он должен сводиться к некоторой арифметике указателей, а не к сравнениям указателей? (Я предполагаю, что конец действительно является концом, а не последним элементом в массиве, если последний затем изменить меньше, чем на <=
).
Я мог бы быть далеко от цели...