Итераторы не защищают от переполнения

Итераторы были введены в С++ в качестве основного механизма доступа к элементам контейнера. Они должны обеспечивать унифицированный объектно-ориентированный доступ к элементам. Но насколько он безопасен? То есть, защищают ли итераторы от индексного переполнения? Давайте проверим.

Рассмотрим небольшую программу:

#include <vector>
#include <stdio.h>

int main()
{
  std::vector<int> vec;
  
  vec.push_back(1);
  vec.push_back(2);
  vec.push_back(3);
  
  std::vector<int>::iterator  it = vec.end();
  it++;
  ++it;
  printf("%d\r\n", *it);
  
  it = vec.begin();
  it += 5;
  printf("%d\r\n", *it);
   
  return 0;
}

В ней умышленно допускается индексное переполнение контейнера с помощью итератора. Посмотрим, как ее обработают различные компиляторы.

GCC, C++ Builder 6 и Embarcadero RAD Studio 10 молча скомпилировали и запустили ее. При этом на экран были выведены сучайные значения (как правило, нули, но не у всех). Никто из них не запустил никакого исключения.

Microsoft Visual Studio 2017 при компиляции отладочной версии приложения выдала ошибку:

По факту здесь сработал assert, который отловил все попытки преполнения. Но работает он только в отладочной версии приложения. Компиляция релизной версии прошла без вопросов. Исключения также не запускаются.

А что по этому поводу говорит стандарт (черновик С++11 №3337). Итераторы описываются в разделе 24. В пункте 24.2.1 говорится:

«Iterators are a generalization of pointers that allows C++ program to work with different data structures (containers) in a uniform manner»

Или в переводе на русский:

«Итераторы — это обобщение указателей, которое позволяет программе на C++ работать с различными структурами данных (контейнерами) в единой манере.»

В том же пункте чуть дальше говорится:

«Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding sequence. These values are called past-the-end values. Values of an iterator i for which the expression *i is defined are called dereferenceable. The library never assumes that past-the-end values are dereferenceable.»

Или по-русски:

«Так же, как обычный указатель на массив, гарантирует, что существует значение указателя, указывающее на последний элемент массива, так и для итератора любого типа существует значение, которое указывает за последний элемент соответствующей последовательности. Эти значения называются конечными (past-the-end) значениями. Значения итератора i, для которых определено выражение *i, называются разыменованными. Библиотека никогда не предполагает, что конечные значения являются разыменованными.»

Как видите, здесь ничего не говорится о защите от индексного переполнения. Забота об этом остается на плечах программиста.

Выводы

Итераторы создавались как унифицированное средство обращения к элементам коллекции произвольного типа. Но они никогда не выступали как средство защиты от индексного переполнения. Как это часто бывает, безопасноть принесена в жертву производительности.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *