Я присваиваю значения в программе на 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;
и этот тоже не дает ошибок.
vector
не автоматически изменяет размер при доступе к элементам за пределами! Это просто UB!Ответы:
Добро пожаловать к лучшему другу каждого программиста на C / C ++: неопределенному поведению .
Существует много того, что не указано в языковом стандарте по разным причинам. Это одна из них.
В общем, всякий раз, когда вы сталкиваетесь с неопределенным поведением, может произойти все что угодно . Приложение может зависнуть, оно может зависнуть, оно может вытолкнуть ваш привод CD-ROM или заставить демонов выйти из носа. Он может отформатировать жесткий диск или по электронной почте всех ваших порно к своей бабушке.
Даже если вам действительно не повезло, может показаться, что он работает правильно.
Язык просто говорит, что должно произойти, если вы обращаетесь к элементам в пределах массива. Не определено, что произойдет, если вы выйдете за пределы. Может показаться, что он работает сегодня на вашем компиляторе, но это не является допустимым C или C ++, и нет никаких гарантий, что он все равно будет работать при следующем запуске программы. Или , что он не имеет перезаписаны важные данные даже сейчас, и вы просто не сталкивались с проблемами, что это будет дело - пока.
Что касается того, почему нет проверки границ, есть несколько аспектов ответа:
std::vector
шаблон класса, который позволяет и то, и другое.operator[]
разработан, чтобы быть эффективным. Стандарт языка не требует, чтобы он выполнял проверку границ (хотя он также не запрещает это). Вектор также имеетat()
функцию-член, которая гарантированно выполняет проверку границ. Так что в C ++ вы получаете лучшее из обоих миров, если используете вектор. Вы получаете массивную производительность без проверки границ, и вы получаете возможность использовать доступ с проверкой границ, когда вы этого хотите.источник
Используя g ++, вы можете добавить параметр командной строки:
-fstack-protector-all
.На вашем примере это привело к следующему:
На самом деле это не поможет вам найти или решить проблему, но, по крайней мере, segfault даст вам понять, что что- то не так.
источник
-fsanitize=address
что ловит эту ошибку как во время компиляции (при оптимизации), так и во время выполнения.-fsanitize=undefined,address
. Но стоит отметить, что есть редкие случаи с библиотекой std, когда доступ за пределы не обнаруживается sanitizer . По этой причине я бы рекомендовал дополнительно использовать-D_GLIBCXX_DEBUG
опцию, которая добавляет еще больше проверок.g ++ не проверяет границы массивов, и вы, возможно, что-то перезаписываете с помощью 3,4, но ничего особенно важного, если вы попытаетесь использовать большее число, вы получите сбой.
Вы просто перезаписываете части стека, которые не используются, вы можете продолжать, пока не достигнете конца выделенного пространства для стека, и в конечном итоге он потерпит крах
РЕДАКТИРОВАТЬ: у вас нет способа справиться с этим, может быть, статический анализатор кода может выявить эти сбои, но это слишком просто, у вас могут быть подобные (но более сложные) сбои, необнаруженные даже для статических анализаторов
источник
Насколько я знаю, это неопределенное поведение. Запустите большую программу с этим, и она потерпит крах где-то по пути. Проверка границ не является частью необработанных массивов (или даже std :: vector).
Используйте std :: vector с
std::vector::iterator
Вместо этого 's', чтобы вам не пришлось об этом беспокоиться.Редактировать:
Просто для удовольствия, запустите это и посмотрите, как долго вы будете падать:
Edit2:
Не запускай это.
Edit3:
Хорошо, вот небольшой урок о массивах и их отношениях с указателями:
Когда вы используете индексирование массива, вы действительно используете скрытый указатель (называемый «ссылкой»), который автоматически разыменовывается. Вот почему вместо * (array [1]) array [1] автоматически возвращает значение с этим значением.
Когда у вас есть указатель на массив, вот так:
Тогда «массив» во втором объявлении действительно превращается в указатель на первый массив. Это поведение эквивалентно этому:
Когда вы пытаетесь получить доступ к тому, что вы выделили, вы на самом деле просто используете указатель на другую память (на что C ++ не будет жаловаться). Взяв мой пример программы выше, это эквивалентно следующему:
Компилятор не будет жаловаться, потому что в программировании вам часто приходится общаться с другими программами, особенно с операционной системой. Это делается с помощью указателей совсем немного.
источник
намек
Если вы хотите иметь быстрые массивы размеров ограничений с проверкой ошибок диапазона, попробуйте использовать boost :: array (также std :: tr1 :: array из
<tr1/array>
него будет стандартным контейнером в следующей спецификации C ++). Это намного быстрее, чем std :: vector. Он резервирует память в куче или внутри экземпляра класса, так же, как int array [].Это простой пример кода:
Эта программа напечатает:
источник
C или C ++ не будут проверять границы доступа к массиву.
Вы размещаете массив в стеке. Индексирование массива через
array[3]
эквивалентно *(array + 3)
, где массив является указателем на & array [0]. Это приведет к неопределенному поведению.Один из способов отловить это иногда в C - использовать статическую проверку, такую как splint . Если вы запускаете:
на,
тогда вы получите предупреждение:
источник
Вы, конечно, перезаписываете свой стек, но программа достаточно проста, чтобы последствия этого остались незамеченными.
источник
Запустите это через Valgrind, и вы можете увидеть ошибку.
Как указал Фалаина, valgrind не обнаруживает много случаев повреждения стека. Я только что попробовал образец под valgrind, и он действительно сообщает об отсутствии ошибок. Тем не менее, Valgrind может быть полезен при обнаружении многих других типов проблем с памятью, в этом случае он просто не особенно полезен, если вы не измените свой bulid для включения опции --stack-check. Если вы создаете и запускаете образец как
Valgrind будет сообщать об ошибке.
источник
Неопределенное поведение работает в вашу пользу. Независимо от того, что память вы бьете, очевидно, не имеет ничего важного. Обратите внимание, что C и C ++ не выполняют проверку границ для массивов, поэтому подобные вещи не будут обнаружены при компиляции или во время выполнения.
источник
Когда вы инициализируете массив с помощью
int array[2]
, выделяется место для 2 целых чисел; но идентификаторarray
просто указывает на начало этого пространства. Когда вы затем обращаетесь кarray[3]
иarray[4]
, компилятор просто увеличивает этот адрес, чтобы указать, где эти значения будут, если массив будет достаточно длинным; попробуйте получить доступ к чему-то вроде,array[42]
не инициализируя это в первую очередь, и вы получите то значение, которое уже было в памяти в этом месте.Редактировать:
Больше информации об указателях / массивах: http://home.netcom.com/~tjensen/ptr/pointers.htm
источник
когда вы объявляете массив int [2]; Вы резервируете 2 пространства памяти по 4 байта в каждом (32-битная программа). если вы введете массив [4] в своем коде, он по-прежнему будет соответствовать допустимому вызову, но только во время выполнения вызовет необработанное исключение. C ++ использует ручное управление памятью. Это на самом деле недостаток безопасности, который был использован для взлома программ
это может помочь понять:
int * somepointer;
somepointer [0] = somepointer [5];
источник
Как я понимаю, локальные переменные размещаются в стеке, поэтому выход за пределы собственного стека может перезаписать только некоторые другие локальные переменные, если вы не слишком перегружены и не превышаете размер стека. Поскольку у вас нет других переменных, объявленных в вашей функции - это не вызывает никаких побочных эффектов. Попробуйте объявить другую переменную / массив сразу после вашей первой и посмотрите, что будет с ней.
источник
Когда вы пишете 'array [index]' в C, это переводит его в машинные инструкции.
Перевод выглядит примерно так:
Результат обращается к чему-то, что может или не может быть частью массива. В обмен на невероятную скорость машинных инструкций вы теряете сеть безопасности компьютера, проверяющего все за вас. Если вы дотошны и осторожны, это не проблема. Если вы неряшливы или допустили ошибку, вас обожгут. Иногда он может генерировать недопустимую инструкцию, которая вызывает исключение, иногда нет.
источник
Хороший подход, который я часто видел и который я использовал на самом деле, заключается в том, чтобы внедрить некоторый элемент типа NULL (или созданный, например,
uint THIS_IS_INFINITY = 82862863263;
) в конец массива.Затем при проверке состояния цикла
TYPE *pagesWords
создается массив указателей:Это решение не скажет, если массив заполнен
struct
типами.источник
Как уже упоминалось в этом вопросе, использование std :: vector :: at решит проблему и сделает связанную проверку перед доступом.
Если вам нужен массив постоянного размера, который находится в стеке в качестве первого кода, используйте новый контейнер C ++ 11 std :: array; в качестве вектора есть функция std :: array :: at. Фактически функция существует во всех стандартных контейнерах, в которых она имеет значение, т. Е. Где определен operator [] :( deque, map, unordered_map), за исключением std :: bitset, в котором она называется std :: bitset: :тест.
источник
libstdc ++, который является частью gcc, имеет специальный режим отладки для проверки ошибок. Это включено флагом компилятора
-D_GLIBCXX_DEBUG
. Помимо прочего, он выполняет проверку границ заstd::vector
счет производительности. Вот онлайн демо с последней версией gcc.Таким образом, на самом деле вы можете выполнять проверку границ в режиме отладки libstdc ++, но вы должны делать это только при тестировании, потому что это стоит ощутимой производительности по сравнению с обычным режимом libstdc ++.
источник
Если вы немного измените свою программу:
(Изменения в столицах - поместите их в нижний регистр, если вы собираетесь попробовать это.)
Вы увидите, что переменная foo была уничтожена. Ваш код будет хранить значения в несуществующем массиве [3] и массиве [4] и сможет правильно их извлекать, но фактическое используемое хранилище будет из foo .
Таким образом, вы можете «уйти» с превышением границ массива в своем первоначальном примере, но ценой нанесения ущерба в другом месте - повреждения, которое может оказаться очень трудно диагностировать.
Что касается того, почему нет автоматической проверки границ - правильно написанная программа не нуждается в этом. Как только это будет сделано, нет причин выполнять проверку границ во время выполнения, и это просто замедлит работу программы. Лучше всего, чтобы все выяснилось при разработке и кодировании.
C ++ основан на C, который был разработан как можно ближе к языку ассемблера.
источник