С std::vector, почему поведение & vec [0] undefined, но vec.data() безопасно?
Я читал FAQ на isocpp.org по адресу "Ссылка здесь" и наткнулся на предостережение, что с std::vector
std::vector<int> v;
auto a = &v[0]; // Is undefined behaviour but
auto a = v.data(); // Is safe
От фактического сайта:
void g()
{
std::vector<Foo> v;
// ...
f(v.begin(), v.size()); // Error, not guaranteed to be the same as &v[0]
↑↑↑↑↑↑↑↑↑ // Cough, choke, gag; use v.data() instead
}
Кроме того, использование &v[0]
- это поведение undefined, если std::vector
или std::array
пуст, в то время как безопасно использовать .data()
функция.
Я не уверен, что понял это точно. ::data()
возвращает указатель на начало массива, а &[0]
возвращает адрес начала. Я не вижу здесь разницы, и я не думаю, что &[0]
разыскивает что-либо (т.е. Не читает память у элемента 0). В Visual Studio в отладочной сборке доступ к индексу [0]
приводит к сбою утверждения, но в режиме выпуска он ничего не говорит. Также адреса в обоих случаях равны 0 для построенного по умолчанию вектора.
Также я не понимаю, что комментарий о ::begin()
не гарантированно совпадает с ::operator[0]
. Я предположил, что для вектора необработанный указатель в итераторе begin()
, ::data()
и &[0]
был одинаковым значением.
Ответы
Ответ 1
Я не вижу здесь разницы.
&v[0]
совпадает с &(v[0])
, т.е. получает адрес из 1-го элемента v
. Но когда v
пуст, элементов вообще нет, v[0]
просто приводит к UB, он пытается вернуть несуществующий элемент; попытка получить адрес от него не имеет смысла.
v.data()
всегда безопасен. Он вернет указатель на базовый массив напрямую. Когда v
пуст, указатель все еще действителен (это может быть нулевой указатель или нет); но обратите внимание, что разыменование его (например, *v.data()
) приводит к UB тоже, так же, как v[0]
.
Также я не понимаю, что комментарий о ::begin()
не гарантированно будет таким же, как ::operator[0]
std::vector::begin
вернет итератор с типом std::vector::iterator
, который должен удовлетворять требованию RandomAccessIterator. Это может быть необработанный указатель, но это не обязательно. Это приемлемо для реализации его как класса.
Ответ 2
Отсутствующая в вашем вопросе информация для вашего примера более понятна: void f(Foo* array, unsigned numFoos);
Вызов .begin()
в вашем векторе Foo
не гарантированно является указателем. Но некоторые реализации могут вести себя так, как будто этого достаточно для работы.
В пустом векторном случае v.data()
возвращает указатель, но вы не знаете, на что он указывает. Это может быть nullptr, но это не гарантируется.
Ответ 3
Все сводится к одной простой вещи: вы можете добавить или вычесть целочисленное значение указателю, но попытка разыменования неверного указателя имеет поведение undefined.
Скажем, например,
int a[10];
int* p = a;
int* q = p + 10; // This is fine
int r = *(p + 10) // This is undefined behaviour
В вашем примере: v[0]
совпадает с *(v internal pointer+0)
, и это проблема, если вектор пуст.