Как происходит переполнение стека и как лучше всего этого избежать или как предотвратить его, особенно на веб-серверах, но были бы интересны и другие примеры?
memory
stack-overflow
Джейсон Майкл
источник
источник
Ответы:
Стек
Стек в этом контексте является буфером «последним пришел - первым вышел», в который вы помещаете данные во время выполнения программы. Последний вошел - первым ушел (LIFO) означает, что последнее, что вы вставляете, всегда первое, что вы получаете обратно - если вы вставляете в стек 2 элемента, 'A', а затем 'B', то первое, что вы вставляете вне стека будет «B», а следующим будет «A».
Когда вы вызываете функцию в своем коде, следующая инструкция после вызова функции сохраняется в стеке и любое пространство памяти, которое может быть перезаписано вызовом функции. Вызываемая функция может использовать больше стека для собственных локальных переменных. Когда это будет сделано, он освобождает используемое пространство стека локальной переменной, а затем возвращается к предыдущей функции.
Переполнение стека
Переполнение стека - это когда вы использовали больше памяти для стека, чем предполагалось использовать вашей программе. Во встроенных системах у вас может быть только 256 байтов для стека, и если каждая функция занимает 32 байта, тогда у вас может быть только вызовы функций глубиной 8 - функция 1 вызывает функцию 2, которая вызывает функцию 3, кто вызывает функцию 4 .... кто вызывает функция 8, которая вызывает функцию 9, но функция 9 перезаписывает память вне стека. Это может привести к перезаписи памяти, кода и т. Д.
Многие программисты делают эту ошибку, вызывая функцию A, которая затем вызывает функцию B, которая затем вызывает функцию C, которая затем вызывает функцию A. Она может работать большую часть времени, но только один раз неправильный ввод заставит ее навсегда войти в этот круг. пока компьютер не обнаружит, что стек слишком раздут.
Рекурсивные функции также являются причиной этого, но если вы пишете рекурсивно (т. Е. Ваша функция вызывает сама себя), вам необходимо знать об этом и использовать статические / глобальные переменные для предотвращения бесконечной рекурсии.
Обычно операционная система и язык программирования, который вы используете, управляют стеком, и это вне ваших рук. Вы должны посмотреть на свой граф вызовов (древовидную структуру, которая показывает из вашего main, что вызывает каждая функция), чтобы увидеть, насколько глубоко заходят ваши вызовы функций, и чтобы обнаружить циклы и рекурсию, которые не предназначены. Преднамеренные циклы и рекурсия необходимо искусственно проверять, чтобы исключить ошибки, если они вызывают друг друга слишком много раз.
Помимо хороших практик программирования, статического и динамического тестирования, на этих высокоуровневых системах мало что можно сделать.
Встроенные системы
Во встраиваемом мире, особенно в коде высокой надежности (автомобильный, авиационный, космический), вы выполняете обширный анализ и проверку кода, но вы также делаете следующее:
Языки и системы высокого уровня
Но на языках высокого уровня, работающих в операционных системах:
Веб-серверы
От вашей «песочницы» зависит, можете ли вы контролировать стек или даже видеть его. Скорее всего, вы можете обращаться с веб-серверами, как с любым другим языком высокого уровня и операционной системой - это в значительной степени не в ваших руках, но проверьте язык и стек серверов, которые вы используете. Это является возможным взорвать стек на вашем SQL сервере, например.
-Адам
источник
Переполнение стека в реальном коде происходит очень редко. Большинство ситуаций, в которых это происходит, представляют собой рекурсии, в которых о завершении было забыто. Однако это может происходить редко в сильно вложенных структурах, например, в особенно больших XML-документах. Единственная реальная помощь здесь - это рефакторинг кода для использования явного объекта стека вместо стека вызовов.
источник
Большинство людей скажут вам, что переполнение стека происходит при рекурсии без пути выхода - хотя в большинстве случаев это правда, если вы работаете с достаточно большими структурами данных, даже правильный путь выхода из рекурсии вам не поможет.
Некоторые варианты в этом случае:
источник
Переполнение стека происходит, когда Джефф и Джоэл хотят дать миру лучшее место для получения ответов на технические вопросы. Слишком поздно предотвратить это переполнение стека. Этот «другой сайт» мог бы предотвратить это, если бы не был грязным. ;)
источник
Бесконечная рекурсия - распространенный способ получить ошибку переполнения стека. Чтобы предотвратить - всегда убедитесь, что есть выход, который будет поражен. :-)
Другой способ получить переполнение стека (по крайней мере, в C / C ++) - объявить в стеке какую-то огромную переменную.
Вот и все.
источник
Обычно переполнение стека является результатом бесконечного рекурсивного вызова (с учетом обычного объема памяти в стандартных компьютерах в настоящее время).
Когда вы вызываете метод, функцию или процедуру, "стандартный" способ или выполнение вызова состоит в:
Итак, обычно это занимает несколько байтов в зависимости от количества и типа параметров, а также от архитектуры машины.
Тогда вы увидите, что если вы начнете делать рекурсивные вызовы, стек начнет расти. Теперь стек обычно резервируется в памяти таким образом, что он растет в направлении, противоположном куче, поэтому при большом количестве вызовов без «возврата» стек начинает заполняться.
Теперь, в старые времена переполнение стека могло произойти просто потому, что вы исчерпали всю доступную память, вот так. С моделью виртуальной памяти (до 4 ГБ в системе X86), которая была вне области видимости, поэтому обычно, если вы получаете ошибку переполнения стека, ищите бесконечный рекурсивный вызов.
источник
Какие? Никто не любит тех, кто зациклен на бесконечном цикле?
источник
Помимо формы переполнения стека, которую вы получаете от прямой рекурсии (например
Fibonacci(1000000)
), более тонкая ее форма, с которой я сталкивался много раз, - это косвенная рекурсия, когда функция вызывает другую функцию, которая вызывает другую, а затем одну из эти функции снова вызывают первую.Обычно это может происходить в функциях, которые вызываются в ответ на события, но сами могут генерировать новые события, например:
В этом случае вызов
ResizeWindow
может вызвать повторный запускWindowSizeChanged()
обратного вызова, который вызываетResizeWindow
снова, пока у вас не закончится стек. В подобных ситуациях вам часто нужно отложить ответ на событие до тех пор, пока кадр стека не вернется, например, отправив сообщение.источник
Учитывая, что это было помечено как «взлом», я подозреваю, что «переполнение стека», о котором он говорит, - это переполнение стека вызовов, а не переполнение стека более высокого уровня, такое как те, которые упоминаются в большинстве других ответов здесь. На самом деле это не относится ни к каким управляемым или интерпретируемым средам, таким как .NET, Java, Python, Perl, PHP и т. Д., На которых обычно пишутся веб-приложения, поэтому ваш единственный риск - это сам веб-сервер, который, вероятно, написан на C или C ++.
Посмотрите эту ветку:
/programming/7308/what-is-a-good-starting-point-for-learning-buffer-overflow
источник
Я воссоздал проблему переполнения стека, получив наиболее распространенное число Фибоначчи, то есть 1, 1, 2, 3, 5 ... так что расчет для fib (1) = 1 или fib (3) = 2 .. fib (n знак равно
для n, скажем, нас будет интересовать - что, если n = 100 000, то каким будет соответствующее число Фибоначчи ??
Подход с одним циклом, как показано ниже -
это довольно просто, и результат -
Теперь другой подход, который я применил, - это Divide и Concur через рекурсию.
т.е. Fib (n) = fib (n-1) + Fib (n-2), а затем дальнейшая рекурсия для n-1 и n-2 ..... до 2 и 1., которая запрограммирована как -
Когда я запустил код для n = 100000, результат будет следующим:
Выше вы можете видеть, что создается StackOverflowError. Теперь причина этого - слишком много рекурсий, так как -
Таким образом, каждая запись в стеке создает еще 2 записи и так далее ... что представлено как -
В конце концов будет создано так много записей, что система не сможет обработать в стеке, и возникнет ошибка StackOverflowError.
Для предотвращения: Для приведенного выше примера - 1. Избегайте использования подхода рекурсии или уменьшайте / ограничивайте рекурсию снова на одно деление уровня, например, если n слишком велико, тогда разделите n, чтобы система могла справиться с его пределом. 2. Используйте другой подход, например, цикл, который я использовал в первом примере кода. (Я вовсе не собираюсь ухудшать Divide & Concur или Recursion, поскольку они являются легендарными подходами во многих самых известных алгоритмах ... я намерен ограничить рекурсию или держаться подальше от нее, если я подозреваю проблемы с переполнением стека)
источник