Сегодня я написал что-то похожее на это:
void foo(std::vector<char>&v){
v.push_back('a');
char*front=&v.front();
char*back=&v.back();
size_t n1=back-front+1;
v.push_back('b');//This could reallocate the vector elements
size_t n2=back-front+1;//Is this line valid or Undefined Behavior ?
}
Если происходит перераспределение, когда я нажимаю ‘b’, могу ли я все же вычислить разницу двух моих указателей?
Прочитав несколько раз соответствующий отрывок стандарта, я все еще не могу решить этот вопрос.
C ++ 11 5.7.6:
Когда два указателя на элементы одного и того же объекта массива вычитаются, результатом является разность
индексы двух элементов массива. Тип результата является определяемым реализацией знаковым интегралом
тип; этот тип должен быть того же типа, который определен как std :: ptrdiff_t в заголовке (18.2). Как
с любым другим арифметическим переполнением, если результат не помещается в предоставленное пространство, поведение не определено.
Другими словами, если выражения P и Q указывают соответственно на i-й и j-й элементы объекта массива,
выражение (P) — (Q) имеет значение i — j, если значение соответствует объекту типа std :: ptrdiff_t.
Более того, если выражение P указывает либо на элемент объекта массива, либо на один элемент после последнего элемента
объект массива, а выражение Q указывает на последний элемент того же объекта массива, выражение
((Q) +1) — (P) имеет то же значение, что и ((Q) — (P)) + 1 и — ((P) — ((Q) +1)), и имеет значение ноль, если
Выражение P указывает на один последний элемент массива, даже если выражение (Q) +1 не
указать на элемент объекта массива. Если оба указателя не указывают на элементы одного и того же объекта массива, или
после последнего элемента объекта массива поведение не определено.
Конечно, я знаю, что это работает, мне просто интересно, законно ли это.
Указатели на удаленные объекты являются токсичными: не трогайте тогда ничего, кроме как присвоение им нового значения. Система отслеживания памяти может перехватывать любое использование восстановленного значения указателя. Однако я не знаю, существует ли такая система.
Соответствующая цитата — пункт 3.7.4.2 [basic.stc.dynamic.deallocation]:
Если аргумент, данный функции освобождения в стандартной библиотеке, является указателем, который не является нулевым значением указателя, функция освобождения должна освободить память, на которую ссылается указатель, делая недействительными все указатели на любую часть выделенное хранилище. Эффект от использования недопустимого значения указателя (включая передачу его функции освобождения) не определен.
При изменении размера std::vector<...>
он перепрыгивает через несколько обручей (распределителей) и, по умолчанию, в конце концов вызывает функцию освобождения.
Строго говоря, это UB. Но вы всегда можете конвертировать ваши char *
указатели на uintptr_t
(при условии, что оно присутствует), а затем безопасно вычесть полученные целые числа.
void foo(std::vector<char>&v){
v.push_back('a');
auto front= uintptr_t (&v.front());
auto back = uintptr_t (&v.back());
size_t n1=back-front+1;
v.push_back('b');//This could reallocate the vector elements
size_t n2=back-front+1;
}
Этот конкретный случай безопасен, но безобразен и вводит в заблуждение.
Линия v.push_back('b');//This could reallocate the vector elements
может вызвать перераспределение вашего контейнера. В этом случае следующая строка будет использовать несуществующий front
а также back
указатели. Вычислительная разница двух адресов безопасна, даже если есть висячие указатели. Что не безопасно, так это разыменование их.
Правильное решение заключается в использовании vector::count()
Функция всегда будет синхронизирована. Если вы (по какой-то причине) не хотите звонить vector::count()
Вы должны по крайней мере использовать ++n1
,