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

С 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] был одинаковым значением.

4b9b3361

Ответ 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), и это проблема, если вектор пуст.