Доступ к элементам в std::string, где положение строки больше ее размера

#c #string #c 11 #language-lawyer

#c #строка #c 11 #язык-юрист

Вопрос:

В случае std::string, если мы обращаемся к элементу, в котором (element position) == (size of string) стандарт говорит, что он возвращает ссылку на объект типа charT со значением charT() .

 const_reference operator[](size_type pos) const;
reference       operator[](size_type pos);
  

Ожидает: pos <= size().

Возвращает: *(begin() pos), если pos < size(). В противном случае возвращает ссылку на объект типа charT со значением charT(), где изменение объекта на любое значение, отличное от charT(), приводит к неопределенному поведению.

http://eel.is/c draft/strings#string.access-1

К сожалению, я не мог рассуждать об этом, было бы лучше, если бы это было неопределенное поведение.

Кто-нибудь может объяснить обоснование этого?

Комментарии:

1. @user463035818 нет, это неправда. Оператор подстрочного индекса [] не выполняет проверку. string::at() делает, и по этой причине он выдает

2. Не приводит ли нарушение «Expects: pos <= size() » сразу к UB? «В противном случае» относится только к pos == size случаю, нет?

3. точно, я думаю, суть в том, что «Ожидает: pos <= size()». если вы не выполняете предварительное условие, вы все равно находитесь в UB, так что речь идет только о доступе к «концу» строки

4. @AImx1 Где в стандарте говорится, что нарушение предложения «Expects» является чем-то иным, чем UB?

5. Для строки в стиле C длиной X использование индекса X даст вам нулевой ограничитель. std::string просто пытается эмулировать это. Выход за пределы всегда приведет к UB.

Ответ №1:

Вы должны учитывать полные спецификации.

Прежде всего:

Ожидает: pos <= size().

Если вы не выполняете предварительное условие, у вас все равно неопределенное поведение. Сейчас…

Возвращает: *(begin() pos), если pos < size(). В противном случае возвращает ссылку на объект типа charT со значением charT(), где изменение объекта на любое значение, отличное от charT(), приводит к неопределенному поведению.

Единственный (допустимый) случай, на который ссылается «иначе», — это когда pos == size() . И это, вероятно, для эмуляции поведения строки c, у которой есть some_string[size] элемент, к которому можно получить доступ. Обратите внимание, что charT() обычно это просто '' .

PS: Можно подумать, что для реализации спецификации operator[] нужно было бы проверить, pos == size если бы. Однако, если базовый массив символов имеет charT() в конце строки, то вы получаете описанное поведение практически бесплатно. Следовательно, то, что кажется немного отличным от «обычного» доступа к массиву, на самом деле именно так и есть.

Комментарии:

1. Отличное объяснение. Также отличный пример того, как объяснение неподдерживаемых случаев (> размер) приводит к усложнению спецификации. ‘в противном случае’ должно было быть ‘if pos==size’

Ответ №2:

Оператор 1 является предварительным условием для оператора 2:

  1. Ожидает: pos <= size() .

  2. Возвращает: *(begin() pos) if pos < size() .

    В противном случае (поэтому здесь единственная жизнеспособная возможность pos == size() ) возвращает ссылку на объект типа charT со значением charT() (т.Е. '' ), где изменение объекта на любое значение, отличное от charT() , приводит к неопределенному поведению.

str[str.size()] в основном указывает на символ нулевого завершения. Вы можете читать и записывать ее, но вы можете записать в нее только '' .

Ответ №3:

Оператор ожидает, что pos значение будет меньше или равно size() , поэтому, если оно не меньше, то ожидается, что оно будет равно.

Ответ №4:

В дополнение к предыдущим ответам, пожалуйста, взгляните на libcxx (реализация llvm) определяет std::string::operator[] , как:

 template <class _CharT, class _Traits, class _Allocator>
inline
typename basic_string<_CharT, _Traits, _Allocator>::const_reference
basic_string<_CharT, _Traits, _Allocator>::operator[](size_type __pos) const _NOEXCEPT
{
    _LIBCPP_ASSERT(__pos <= size(), "string index out of bounds");
     return *(data()   __pos);
}

template <class _CharT, class _Traits, class _Allocator>
inline
typename basic_string<_CharT, _Traits, _Allocator>::reference
basic_string<_CharT, _Traits, _Allocator>::operator[](size_type __pos) _NOEXCEPT
{
    _LIBCPP_ASSERT(__pos <= size(), "string index out of bounds");
    return *(__get_pointer()   __pos);
}
  

Взгляните на .at() , который правильно выдает вместо этого.

 template <class _CharT, class _Traits, class _Allocator>
typename basic_string<_CharT, _Traits, _Allocator>::const_reference
basic_string<_CharT, _Traits, _Allocator>::at(size_type __n) const
{
    if (__n >= size())
        this->__throw_out_of_range();
    return (*this)[__n];
}
  

Как вы можете, в первом случае существует runtime assert (спасибо t.niese за указание), который запускается только в режиме отладки, тогда как второй всегда будет выдавать, независимо от параметров сборки библиотеки.

Комментарии:

1. Это не статическое утверждение , это утверждение во время выполнения. static_assert — это то, что проверяется во время компиляции, а статическое утверждение выполняется как для релизной, так и для отладочной сборки.

2. std::string name = "StackOverflow"; std::cout << name[100]; @t.niese Если это утверждение во время выполнения, почему приведенный ниже код не завершается сбоем?

3. @AImx1 потому что вы не указали, что хотите отладочную сборку при создании библиотеки?

4. @AImx1 потому что стандарт говорит, что name[100] это неопределенное поведение, а не то, что оно должно выдавать. _LIBCPP_ASSERT это утверждение отладки, которое должно быть явно включено и не включается автоматически для обычных отладочных сборок, и оно зависит от времени выполнения llvm: DebugMode

5. Кроме того, если оставить в стороне _LIBCPP_ASSERT , что действительно показывает этот фрагмент кода, так это отсутствие подобного теста if (__pos >= size()) return _CharT() , который был бы необходим для поведения, ожидаемого в исходном вопросе. Без подобного теста единственный способ _CharT() вернуть его — это сохранить в буфере, на который указывает data() . Очевидно, что это не может иметь место для всех возможных значений __pos , если буфер не занимает всю память на вашем компьютере!