Доступ к массиву вне границ не дает ошибок, почему? - PullRequest
150 голосов
/ 06 августа 2009

Я присваиваю значения в программе на C ++ вне границ:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    return 0;
}

Программа печатает 3 и 4. Это не должно быть возможно. Я использую g ++ 4.3.3

Вот команда компиляции и запуска

$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4

Только при присвоении array[3000]=3000 это дает мне ошибку сегментации.

Если gcc не проверяет границы массивов, как я могу быть уверен, что моя программа верна, поскольку это может привести к серьезным проблемам позже?

Я заменил вышеуказанный код на

vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;

и этот тоже не выдаёт ошибки.

Ответы [ 17 ]

0 голосов
/ 11 мая 2015

Как уже упоминалось в вопросе, использование std :: vector :: at решит проблему и выполнит обязательную проверку перед доступом.

Если вам нужен массив постоянного размера, который находится в стеке в качестве первого кода, используйте новый контейнер C ++ 11 std :: array; в качестве вектора есть функция std :: array :: at. Фактически функция существует во всех стандартных контейнерах, в которых она имеет значение, т. Е. Где определен operator [] :( deque, map, unordered_map), за исключением std :: bitset, в котором она называется std :: bitset: :. тест

0 голосов
/ 09 октября 2013

Хороший подход, который я часто видел и который я использовал на самом деле, заключается в том, чтобы вставить какой-либо элемент типа NULL (или созданный, например, uint THIS_IS_INFINITY = 82862863263;) в конец массива.

Тогда при проверке состояния цикла TYPE *pagesWords - это некий массив указателей:

int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]);

realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1);

pagesWords[pagesWordsLength] = MY_NULL;

for (uint i = 0; i < 1000; i++)
{
  if (pagesWords[i] == MY_NULL)
  {
    break;
  }
}

В этом решении не будет слов, если массив заполнен struct типами.

0 голосов
/ 06 августа 2009

Когда вы пишете 'array [index]' в C, это переводит его в машинные инструкции.

Перевод выглядит примерно так:

  1. 'получить адрес массива'
  2. 'получить размер типа массива объектов, состоящего из'
  3. 'умножить размер шрифта на индекс'
  4. 'добавить результат к адресу массива'
  5. «прочитайте, что по полученному адресу»

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

0 голосов
/ 06 августа 2009

Как я понимаю, локальные переменные размещаются в стеке, поэтому выход за пределы вашего собственного стека может перезаписать только некоторые другие локальные переменные, если вы не слишком перегружены и не превышаете размер стека. Поскольку у вас нет других переменных, объявленных в вашей функции - это не вызывает никаких побочных эффектов. Попробуйте объявить другую переменную / массив сразу после вашей первой и посмотрите, что будет с ней.

0 голосов
/ 10 июня 2017

libstdc ++, который является частью gcc, имеет специальный режим отладки для проверки ошибок. Включается флагом компилятора -D_GLIBCXX_DEBUG. Помимо прочего, он выполняет проверку границ для std::vector за счет производительности. Вот онлайн демо с последней версией gcc.

Таким образом, на самом деле вы можете выполнять проверку границ в режиме отладки libstdc ++, но вы должны делать это только при тестировании, потому что это стоит ощутимой производительности по сравнению с обычным режимом libstdc ++.

0 голосов
/ 08 ноября 2017

Если вы немного измените свою программу:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    INT NOTHING;
    CHAR FOO[4];
    STRCPY(FOO, "BAR");
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    COUT << FOO << ENDL;
    return 0;
}

(Изменения в заглавных буквах - ставьте их в нижнем регистре, если вы собираетесь попробовать это).

Вы увидите, что переменная foo уничтожена. Ваш код будет сохранять значения в несуществующем массиве [3] и массиве [4] и сможет правильно их извлекать, но фактическое используемое хранилище будет из foo .

Таким образом, вы можете «уйти» с превышением границ массива в вашем первоначальном примере, но ценой нанесения ущерба в другом месте - повреждения, который может оказаться очень трудно диагностируемым.

Относительно того, почему нет автоматической проверки границ - правильно написанной программе это не нужно. Как только это будет сделано, нет причин выполнять проверку границ во время выполнения, и это просто замедлит работу программы. Лучше всего, чтобы все выяснилось при разработке и кодировании.

C ++ основан на C, который был разработан как можно ближе к языку ассемблера.

0 голосов
/ 06 августа 2009

когда вы объявляете массив int [2]; Вы резервируете 2 пространства памяти по 4 байта в каждом (32-битная программа). если вы введете массив [4] в своем коде, он по-прежнему будет соответствовать допустимому вызову, но только во время выполнения вызовет необработанное исключение C ++ использует ручное управление памятью. На самом деле это уязвимость безопасности, которая использовалась для взлома программ

это может помочь пониманию:

int * somepointer;

somepointer [0] = somepointer [5];

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...