Поведение std::advance в конце std::list

#include <iostream>
#include <list>
using namespace std;

int main() {
  list<int> A;
  A.push_back(1);
  A.push_back(2);
  A.push_back(3);

  auto it = A.begin();
  cout << *it << ' ';
  advance(it, 1);
  cout << *it << ' ';
  advance(it, 1);
  cout << *it << ' ';
  advance(it, 1);
  cout << *it << ' ';
  advance(it, 1);
  cout << *it << ' ';
}

Я считаю, что list — это двусвязный список. Удивительно, но результат равен 1 2 3 3 1. Может кто-нибудь объяснить, что здесь происходит? Вот вопросы:

  1. Когда мы продвигаемся дважды, мы находимся в узле с 3. Здесь дополнительный advance перемещает итератор на A.end(). Я считаю, что A.end() — это воображаемый узел, который не указывает на исходный элемент. Однако он распечатывает 3. Почему?
  2. Почему в конце выводится 1? list — это не круговая очередь.

Добро пожаловать в страну неопределенного поведения, где все, что происходит, правильно.

NathanOliver 08.12.2022 21:13

Вы, кажется, делаете поспешный вывод, что 1, который вы видите, — это 1 из начала списка, но это всего лишь 1. Даже если бы следующие числа были 2 и 3, вы никак не могли бы сказать, что это числа в списке. Во всяком случае, его undefined и вывод может быть любым

463035818_is_not_a_number 08.12.2022 21:15

Ясно, это означает, что я должен проверить диапазон перед использованием std::advance. Спасибо за ваш вклад!

Hee Hwang 08.12.2022 21:19

Да, никогда не забывайте о проверке диапазона! STL (который был основой этих контейнеров и алгоритмов) был разработан как острый инструмент для людей, которые обращают внимание. Нет сетей, которые вас поймают, но и штрафа за производительность вы не платите. Большинство реализаций в настоящее время поставляются с диагностическим режимом, который может уберечь вас от этого в целях разработки. Однако вам нужно активировать его явно. Как это сделать, должно быть задокументировано.

Ulrich Eckhardt 08.12.2022 21:29

или вы знаете, что элементов достаточно без проверки. Я имею в виду, что в этом текущем примере вы также знаете, что выходите за пределы списка, проверка действительно не нужна.

463035818_is_not_a_number 08.12.2022 21:37

@UlrichEckhardt MSVC хорош в этом, многие проверки включаются автоматически в отладочных сборках.

Paul Sanders 08.12.2022 21:47

Помимо проверки итератора перед его продвижением, необходимо проверить его перед разыменованием. Если it является конечным итератором, то оценка *it и/или вызов advance(it, ...) дают неопределенное поведение.

Peter 08.12.2022 22:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
89
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Разыменование конечного итератора не определено. Конечный итератор указывает на один после последнего элемента в списке. Он не относится к элементу в списке, потому что после последнего элемента нет.

Дальнейшее продвижение конечного итератора также не определено.

Ваша программа имеет неопределенное поведение.

Неопределенное поведение означает, что все может случиться. Поведение кода во время выполнения без неопределенного поведения указано в стандарте языка (некоторые из них определены или не определены реализацией, но давайте не будем усложнять). С другой стороны, когда ваш код имеет неопределенное поведение, вы не можете гарантировать, каким будет результирующее поведение программы во время выполнения. Ваш результат может быть "Hello World".

Конечно, результат не "Hello World", и есть причина, по которой вы видите результат, который видите. Однако эти причины не связаны с тем, как работает C++. Чтобы понять, почему вы получаете такой вывод, вам нужно изучить сборку, сгенерированную компилятором. В конце концов, чтобы понять, почему компилятор сгенерировал эту сборку, вам нужно будет изучить детали реализации вашего компилятора. Однако, если вы хотите узнать о C++, то все это бесполезно, и все, что вам нужно знать, это то, что ваш код имеет неопределенное поведение.

..... и (за исключением особых случаев использования, таких как преднамеренное использование специфичных для компилятора расширений), что лучше всего писать код (выполнять все необходимые проверки), чтобы избежать неопределенного поведения.

Peter 08.12.2022 22:59

Я тестировал программу, и она вылетела

Другие вопросы по теме