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

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

#include <iostream>
#include <cstdio>
#include <vector>

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

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

11

Решение

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

В 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<int> v(n);
return v[k];
}

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

int main() try
{
int n = read_int();
int k = read_int();
std::vector<int> 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<int> v(n);
return v[k];
}

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

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

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

13

Другие решения

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

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

5

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

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

#include <vector>

using namespace std;

template <class _Tp, class _Allocator = allocator<_Tp> >
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<value_type> __il):vector<_Tp, _Allocator>(__il){;}
template <class _Iterator>
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
3

Это ценный комментарий @Evgeny Sergeev, который я предлагаю в ответ:

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

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

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

2
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector