C ++: векторные границы

Я прихожу из Java и изучаю C ++ в данный момент. Я использую Принципы Программирования и Практику использования С ++. Сейчас я работаю с векторами. На стр. 117 он говорит, что доступ к несуществующему элементу вектора вызовет ошибку времени выполнения (то же самое в Java, индекс за пределами границ). Я использую компилятор MinGW и когда компилирую и запускаю этот код:

#include  #include  #include  int main() { std::vector v(6); v[8] = 10; std::cout << v[8]; return 0; } 

Это дает мне результат 10. Еще более интересно то, что если я не изменю несуществующий векторный элемент (я просто печатаю его, ожидая ошибки во время выполнения или, по крайней мере, значения по умолчанию), он печатает некоторые большие целые числа. Итак … Неужели Stroustrup ошибается, или у GCC есть некоторые странные способы компиляции C ++?

Книга немного расплывчата. Это не столько «ошибка времени выполнения», сколько неопределенное поведение, которое проявляется во время выполнения. Это означает, что все может случиться . Но ошибка строго связана с вами , а не с выполнением программы, и на самом деле невозможно и неразумно даже говорить о выполнении программы с неопределенным поведением.

В C ++ ничего нет, что защищает вас от ошибок программирования, в отличие от Java.


Как говорит @sftrabbit, std::vector имеет альтернативный интерфейс, .at() , который всегда дает правильную программу (хотя он может генерировать исключения), и, следовательно, тот, о котором можно рассуждать.


Позвольте мне повторить этот пример с примером, потому что я считаю, что это важный фундаментальный аспект C ++. Предположим, мы читаем целое число от пользователя:

 int read_int() { std::cout << "Please enter a number: "; int n; return (std::cin >> n) ? n : 18; } 

Теперь рассмотрим следующие три программы:

Опасный: правильность этой программы зависит от пользовательского ввода! Это не обязательно неверно , но это небезопасно (до такой степени, что я бы назвал его сломанным).

 int main() { int n = read_int(); int k = read_int(); std::vector v(n); return v[k]; } 

Безоговорочно корректно: независимо от того, что вводит пользователь, мы знаем, как ведет себя эта программа.

 int main() try { int n = read_int(); int k = read_int(); std::vector v(n); return v.at(k); } catch (...) { return 0; } 

Разумный: вышеприведенная версия с .at() неудобна. Лучше проверить и предоставить обратную связь. Поскольку мы выполняем динамическую проверку, неконтролируемый доступ к вектору на самом деле гарантированно хорош.

 int main() { int n = read_int(); if (n <= 0) { std::cout << "Bad container size!\n"; return 0; } int k = read_int(); if (k < 0 || k >= n) { std::cout << "Bad index!\n"; return 0; } std::vector v(n); return v[k]; } 

(Мы игнорируем возможность того, что конструкция вектора может исключить ее собственную.)

Мораль в том, что многие операции на C ++ являются небезопасными и только условно правильными, но от программиста ожидается, что вы сделаете необходимые проверки раньше времени. Язык не делает этого для вас, и поэтому вы не платите за него, но вы должны помнить об этом. Идея состоит в том, что вам все равно придется обрабатывать условия ошибки, и поэтому вместо того, чтобы выполнять дорогостоящую, неспецифическую операцию на уровне библиотеки или языка, ответственность остается за программистом, который находится в лучшем положении для интеграции проверки в код, который должен быть написан в любом случае.

Если бы я хотел быть преувеличенным, я бы сравнил этот подход с Python, который позволяет писать невероятно короткие и правильные программы без какой-либо обработки ошибок пользователем. Оборотная сторона заключается в том, что любая попытка использовать такую ​​программу, которая немного отличается от того, что предлагает программист, оставляет вам неспецифическую, трудночитаемую исключение и трассировку стека, а также небольшие рекомендации относительно того, что вы должны были сделать лучше. Вы не обязаны писать какую-либо обработку ошибок, и часто обработка ошибок не записывается. (Я не могу отличить C ++ от Java, потому что, хотя Java вообще безопасен , мне еще предстоит увидеть короткую Java-программу.)

C и C ++ не всегда выполняют проверки границ. Это может привести к ошибке выполнения. И если вам нужно переусердствовать с вашим номером достаточно, скажем, 10000 или около того, это почти наверняка вызовет проблему.

Вы также можете использовать vector.at (10), что обязательно должно дать вам исключение. см .: http://www.cplusplus.com/reference/vector/vector/at/ по сравнению с: http://www.cplusplus.com/reference/vector/vector/operator%5B%5D/

Я надеялся, что «оператор []» вектора будет проверять границу как «at ()», потому что я не так осторожен. 🙂

Один из способов наследовал бы векторный class и оператор переопределения [] для вызова в (), чтобы можно было использовать более читаемые «[]» и не нужно было заменять все «[]» на «at ()». Вы также можете определить унаследованный вектор (например: safer_vector) как обычный вектор. Код будет таким (в C ++ 11, llvm3.5 Xcode 5).

 #include  using namespace std; template  > class safer_vector:public vector<_Tp, _Allocator>{ private: typedef __vector_base<_Tp, _Allocator> __base; public: typedef _Tp value_type; typedef _Allocator allocator_type; typedef typename __base::reference reference; typedef typename __base::const_reference const_reference; typedef typename __base::size_type size_type; public: reference operator[](size_type __n){ return this->at(__n); }; safer_vector(_Tp val):vector<_Tp, _Allocator>(val){;}; safer_vector(_Tp val, const_reference __x):vector<_Tp, _Allocator>(val,__x){;}; safer_vector(initializer_list __il):vector<_Tp, _Allocator>(__il){;} template  safer_vector(_Iterator __first, _Iterator __last):vector<_Tp,_Allocator>(__first, __last){;}; // If C++11 Constructor inheritence is supported // using vector<_Tp, _Allocator>::vector; }; #define safer_vector vector 

Это ценный комментарий от Евгения Сергеева, что я продвигаю ответ:

Для GCC вы можете -D_GLIBCXX_DEBUG заменить стандартные контейнеры безопасными реализациями. Совсем недавно это, похоже, также работает с std :: array. Подробнее здесь: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html

Я бы добавил, что также можно объединить отдельные «безопасные» версии векторных и других classов полезности, используя префикс gnu_debug :: namespace, а не std ::.

Другими словами, не заново изобретайте колесо, проверки массива доступны, по крайней мере, с помощью GCC.