#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:
Ожидает:
pos <= size()
.Возвращает:
*(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: DebugMode5. Кроме того, если оставить в стороне
_LIBCPP_ASSERT
, что действительно показывает этот фрагмент кода, так это отсутствие подобного тестаif (__pos >= size()) return _CharT()
, который был бы необходим для поведения, ожидаемого в исходном вопросе. Без подобного теста единственный способ_CharT()
вернуть его — это сохранить в буфере, на который указываетdata()
. Очевидно, что это не может иметь место для всех возможных значений__pos
, если буфер не занимает всю память на вашем компьютере!