Какая ошибка программирования обычно приводит к переполнению стека во время выполнения программы

Обновлено: 04.07.2024

Я искал везде и не могу найти твердый ответ. Согласно документации, Java бросает java.ленг.StackOverflowError ошибка при следующих обстоятельствах:

возникает, когда стек переполнение происходит, потому что приложение повторяется слишком глубоко.

но это вызывает два вопроса:

  • а разве нет других способов для переполнения стека происходит не только через рекурсию?
  • StackOverflowError происходит до того, как JVM фактически переполняет стек или после?

чтобы уточнить второй вопрос:

когда Java бросает StackOverflowError, вы можете с уверенностью предположить, что стек не сделал написать в кучу? Если вы уменьшите размер стека или кучи в try / catch на функцию, которая вызывает переполнение стека, вы можете продолжить работу? Это где-нибудь задокументировано?

ответы, которые я не ищу:

  • StackOverflow происходит из-за плохой рекурсии.
  • StackOverflow происходит, когда куча встречается со стеком.

кажется, вы думаете, что a ошибка stackoverflow похоже на исключение переполнения буфера в собственных программах, когда существует риск записи в память, которая не была выделена для буфера, и, таким образом, повредить некоторые другие места памяти. Это не так.

JVM имеет заданную память, выделенную для каждого стека каждого потока, и если попытка вызвать метод происходит, чтобы заполнить эту память, JVM выдает ошибку. Как это сделать, если вы были попытка записи по индексу N массива длины N. никакого повреждения памяти произойти не может. Стек не может писать в кучу.

StackOverflowError для стека-это то же, что OutOfMemoryError для кучи: он просто сигнализирует, что больше нет доступной памяти.

описание ошибок виртуальной машины (§6.3)

StackOverflowError: реализация виртуальной машины Java исчерпала пространство стека для a поток, как правило, потому, что поток выполняет неограниченное количество рекурсивных вызовов в результате ошибки в выполняющейся программе.

а разве нет других способов для переполнения стека происходит не только через рекурсию?

конечно. Просто продолжайте вызывать методы, никогда не возвращаясь. Вам понадобится много методов, если вы не позволите рекурсии. На самом деле, это не имеет значения: кадр стека-это кадр стека, независимо от того, является ли он одним из рекурсивных методов или нет.

ответ на ваш второй вопрос: stackoverflow обнаруживается, когда JVM пытается выделите кадр стека для следующего вызова, и находит это невозможно. Таким образом, ничего не будет перезаписано.

нет ли других способов для переполнения стека, не только через рекурсию?

Вызов принят :) StackOverflowError без рекурсии (вызов не удалось, см. комментарии):

компиляция со стандартом javac Test.java и запускать с java -Xss104k Test 2> out . После этого, more out скажу вам:

вторая попытка.

теперь идея еще проще. Примитивы в Java могут храниться на стек. Итак, давайте объявим много двойников, как double a1,a2,a3. . Этот скрипт может писать, компилировать и запускать код о нас:

и. Я получил что-то неожиданное:

это 100% повторяется. Это связано с вашим вторым вопросом:

StackOverflowError происходит до того, как JVM фактически переполняется стек или после?

Итак, в случае OpenJDK 20.0-b12 мы можем видеть, что JVM во-первых взорванный. Но это похоже на ошибку, может быть, кто-то может подтвердить это в комментариях, пожалуйста, потому что я не уверен. Должен ли я сообщить об этом? Может быть, это уже исправлено в какой-то новой версии. Согласно ссылка на спецификацию JVM (дан JB Nizet в комментарии) JVM должен бросить StackOverflowError , не умирают:

если вычисление в потоке требует большей виртуальной машины Java стек, чем разрешено, виртуальная машина Java бросает StackOverflowError.

третья попытка.

мы хотим создать новый

наиболее распространенной причиной StackOverFlowError является чрезмерно глубокая или бесконечная рекурсия.

например:

В Java:

здесь two области в памяти куча и стек. Элемент stack memory используется для хранения локальных переменных и вызов функции, в то время как heap memory используется для хранения объектов в Java.

если в стеке не осталось памяти для хранения вызова функции или локальной переменной, JVM бросит java.lang.StackOverFlowError

в то время как если там нет больше места кучи для создания объекта, JVM будет бросать java.lang.OutOfMemoryError

  • нет, это единственный способ получить эту ошибку: получение ваш стек полный. Но не только через рекурсию, но и вызывая методы, которые бесконечно вызывают другие методы. Это очень специфическая ошибка, так что нет.
  • он выбрасывается до того, как стек заполнен, именно тогда, когда вы его проверяете. Куда бы вы поместили данные, если нет свободного места ? Перекрывая других ? Неа.

есть два основных места, что вещи могут храниться в Java. Первый-это куча, которая используется для динамически выделяемых объектов. new .

кроме того, каждый поток получает свой собственный стек, и он получает объем памяти, выделенной для этого стека.

при вызове метода данные помещаются в стек для записи вызова метода, передаваемых параметров и выделяемых локальных переменных. Метод с пятью локальными переменными и тремя параметры будут использовать больше пространства стека, чем a void doStuff() метод без локальных переменных.

основные преимущества стека заключаются в том, что нет фрагментации памяти, все для одного вызова метода выделяется в верхней части стека, и что возврат из методов прост. Чтобы вернуться из метода, вы просто разматываете стек обратно к предыдущему методу, устанавливаете любое значение, необходимое для возвращаемого значения, и все готово.

потому что стек имеет фиксированный размер на поток, (обратите внимание, что спецификация Java не требует фиксированного размера, но большинство реализаций JVM на момент написания используют фиксированный размер) и поскольку пространство в стеке необходимо всякий раз, когда вы делаете вызов метода, надеюсь, теперь должно быть ясно, почему он может закончиться и что может привести к его завершению. Существует не фиксированное количество вызовов метода, нет ничего конкретного о рекурсии, вы получаете исключение, которое вы пытаетесь вызвать метод и не хватает памяти.

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

StackOverflow происходит, когда выполняется вызов функции и стек заполнен.

Так же, как ArrayOutOfBoundException. Он не может ничего испортить, на самом деле его очень легко поймать и восстановить.

обычно это происходит в результате неконтролируемой рекурсии, но это также может быть вызвано просто очень глубоким вызовом стека функций.

  1. а разве нет других способов для переполнения стека происходит не только через рекурсию?
  2. StackOverflowError происходит до того, как JVM фактически переполняет стек или после?

Это также может произойти, когда мы выделяем размер больше, чем предел стека (например. int x[10000000]; ).

ответ на второй

каждый поток имеет свой собственный стек, который содержит фрейм для каждого метода, выполняющегося в этом потоке. Таким образом, текущий выполняемый метод находится в верхней части стека. Новый фрейм создается и добавляется (выталкивается) в верхнюю часть стека для каждого вызова метода. Кадр удаляется (выскакивает), когда метод возвращает нормально или если во время вызова метода возникает неперехваченное исключение. Стек не управляется напрямую, за исключением объектов push и pop frame, а также поэтому объекты фрейма могут быть выделены в куче, и память не должна быть непрерывной.

поэтому, рассматривая стек в потоке, мы можем сделать вывод.

стек может быть динамического или фиксированного размера. Если поток требует большего стека, чем разрешено StackOverflowError бросается. Если поток требует нового кадра и недостаточно памяти для его выделения, то OutOfMemoryError бросается.

Как происходит переполнение стека и каковы наилучшие способы убедиться, что этого не происходит, или способы предотвратить это, особенно на веб-серверах, но другие примеры также были бы интересны?

стек, в этом контексте, является последним буфером in, first out, который вы размещаете данные во время выполнения программы. Last in, first out (LIFO) означает, что последнее, что вы кладете, всегда первое, что вы получаете обратно - если вы нажимаете 2 элемента в стеке, "A", а затем "B", то первое, что вы выскочите из стека, будет "B", а следующее - "A".

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

переполнение стека

переполнение стека, когда вы использовали больше памяти для стека, чем ваша программа должна использовать. Во встроенных системах может быть только 256 байт для стека, и если каждый функция занимает 32 байта, тогда вы можете иметь только вызовы функций 8 глубоких функций 1 вызывает функцию 2 кто вызывает функцию 3 кто вызывает функцию 4 . кто вызывает функцию 8, кто вызывает функцию 9, но функция 9 перезаписывает память вне стека. Это может перезаписать память, код и т. д.

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

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

Как правило, ОС и язык программирования, который вы используете, управляют стеком, и это не в ваших руках. Вы должны посмотреть на ваш звонок graph (древовидная структура, которая показывает из вашего main, что вызывает каждая функция), чтобы увидеть, как глубоко ваши вызовы функций идут, и обнаружить циклы и рекурсию, которые не предназначены. Преднамеренные циклы и рекурсия должны быть искусственно проверены на ошибку, если они вызывают друг друга слишком много раз.

помимо хороших методов программирования, статического и динамического тестирования, вы мало что можете сделать в этих системах высокого уровня.

встраиваемых систем

во встроенном мир, особенно в коде высокой надежности (автомобильный, самолет, космос) Вы делаете обширные обзоры кода и проверку, но вы также делаете следующее:

  • запретить рекурсию и циклы-принудительно с помощью политики и тестирования
  • держите код и стек далеко друг от друга (код во вспышке, стек в ОЗУ, и никогда два не должны встретиться)
  • поместите полосы защиты вокруг стека-пустая область памяти, которую вы заполняете магическим числом (обычно прерывание программного обеспечения инструкция, но здесь есть много вариантов), и сотни или тысячи раз в секунду вы смотрите на полосы охраны, чтобы убедиться, что они не были перезаписаны.
  • использовать защиту памяти (т. е. не выполнять в стеке, не читать и не писать только вне стека)
  • прерывания не вызывают вторичные функции - они устанавливают флаги, копируют данные и позволяют приложению заботиться об их обработке (в противном случае вы можете получить 8 глубоко в дереве вызовов функций, иметь прерывание и затем выйдите еще несколько функций внутри прерывания, вызывая выброс). У вас есть несколько деревьев вызовов - по одному для основных процессов и по одному для каждого прерывания. Если ваши прерывания могут прерывать друг друга. ну, есть драконы.

языки и системы высокого уровня

но в языках высокого уровня, работающих в операционных системах:

  • уменьшите локальное хранилище переменных (локальные переменные хранятся в стеке-хотя компиляторы довольно умный об этом и иногда будет помещать больших местных жителей в кучу, если ваше дерево вызовов мелкое)
  • избегайте или строго ограничить рекурсию
  • не разбивайте свои программы слишком далеко на все меньшие и меньшие функции-даже без подсчета локальных переменных каждый вызов функции потребляет целых 64 байта в стеке (32-битный процессор, сохраняя половину регистров процессора, флаги и т. д.)
  • держите дерево вызовов неглубоким (аналогично приведенному выше заявление)

веб-сервера

это зависит от "песочницы" у вас есть, можете ли вы контролировать или даже видеть стек. Скорее всего, вы можете относиться к веб - серверам, как к любому другому языку высокого уровня и операционной системе-это в основном из ваших рук, но проверьте язык и стек серверов, которые вы используете. Это is можно взорвать стек на вашем SQL server, например.

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

бесконечная рекурсия-это простой способ получить ошибку переполнения стека. Чтобы предотвратить - всегда убедитесь, что есть путь выхода, что будет быть хитом. :-)

другой способ получить переполнение стека (по крайней мере, в C/C++) - объявить некоторую огромную переменную в стеке.

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

некоторые опции в этом случае:

обычно переполнение стека является результатом бесконечного рекурсивного вызова (учитывая обычный объем памяти в настоящее время для обычных компьютеров).

при вызове метода, функции или процедуры "стандартный" способ или вызов состоит в следующем:

  1. нажатие направления возврата для вызова в стек (это следующее предложение после вызова)
  2. обычно пространство для возвращаемого значения резервируется в стек
  3. нажатие каждого параметра в стек (порядок расходится и зависит от каждого компилятора, также некоторые из них иногда хранятся в регистрах процессора для повышения производительности)
  4. выполнение фактического вызова.

таким образом, обычно это занимает несколько байтов, уменьшающих количество и тип параметров, а также архитектуру машины.

вы увидите, что если вы начнете делать рекурсивные вызовы, стек начнет расти. Сейчас, стек обычно резервируется в памяти таким образом, что он растет в противоположном направлении к куче, поэтому при большом количестве вызовов без "возврата" стек начинает заполняться.

теперь, в старые времена переполнение стека может произойти просто потому, что вы exausted всю доступную память, просто так. С моделью виртуальной памяти (до 4 ГБ в системе X86), которая была вне области, поэтому обычно, если вы получаете ошибку переполнения стека, ищите бесконечный рекурсивный вызов.

переполнение стека происходит, когда Джефф и Джоэл хотите дать миру лучшее место, чтобы получить ответы на технические вопросы. Слишком поздно предотвращать переполнение стека. Этот "другой сайт" мог бы предотвратить это, не будучи scuzzy. ;)

Помимо формы переполнения стека, которую вы получаете от прямой рекурсии (например, Fibonacci(1000000) ), более тонкой формой этого, которую я испытывал много раз, является косвенная рекурсия, где функция вызывает другую функцию, которая вызывает другую, а затем одна из этих функций снова вызывает первую.

это обычно может происходить в функциях, которые вызываются в ответ на события, но которые сами могут генерировать новые события, например:

что? Никто не любит тех, кого окружает бесконечная петля?

Сегмент кода (или «текстовый сегмент»), где находится скомпилированная программа. Обычно доступен только для чтения.

Сегмент bss (или «неинициализированный сегмент данных»), где хранятся глобальные и статические переменные, инициализированные нулем.

Сегмент данных (или «сегмент инициализированных данных»), где хранятся инициализированные глобальные и статические переменные.

Куча, откуда выделяются динамические переменные.

Стек вызовов, где хранятся параметры функции, локальные переменные и другая информация, связанная с функциями.

Сегмент кучи (или просто «куча») отслеживает память, используемую для динамического выделения. Мы уже немного поговорили о куче на уроке о динамическом выделении памяти в языке С++.

В языке C++ при использовании оператора new динамическая память выделяется из сегмента кучи самой программы:

int * ptr = new int ; // для ptr выделяется 4 байта из кучи int * array = new int [ 10 ] ; // для array выделяется 40 байт из кучи

Адрес выделяемой памяти передается обратно оператором new и затем он может быть сохранен в указателе. О механизме хранения и выделения свободной памяти нам сейчас беспокоиться незачем. Однако стоит знать, что последовательные запросы памяти не всегда приводят к выделению последовательных адресов памяти!

// ptr1 и ptr2 могут не иметь последовательных адресов памяти

При удалении динамически выделенной переменной, память возвращается обратно в кучу и затем может быть переназначена (исходя из последующих запросов). Помните, что удаление указателя не удаляет переменную, а просто приводит к возврату памяти по этому адресу обратно в операционную систему.

Куча имеет свои преимущества и недостатки:

Выделение памяти в куче сравнительно медленное.

Выделенная память остается выделенной до тех пор, пока не будет освобождена (остерегайтесь утечек памяти) или пока программа не завершит свое выполнение.

Доступ к динамически выделенной памяти осуществляется только через указатель. Разыменование указателя происходит медленнее, чем доступ к переменной напрямую.

Поскольку куча представляет собой большой резервуар памяти, то именно она используется для выделения больших массивов, структур или классов.

Стек вызовов

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

Стек вызовов реализуется как структура данных «Стек». Поэтому, прежде чем мы поговорим о том, как работает стек вызовов, нам нужно понять, что такое стек как структура данных.

Стек как структура данных

Структура данных в программировании — это механизм организации данных для их эффективного использования. Вы уже видели несколько типов структур данных, например, массивы или структуры. Существует множество других структур данных, которые используются в программировании. Некоторые из них реализованы в Стандартной библиотеке C++, и стек как раз является одним из таковых.

Например, рассмотрим стопку (аналогия стеку) тарелок на столе. Поскольку каждая тарелка тяжелая, а они еще и сложены друг на друге, то вы можете сделать лишь что-то одно из следующего:

Посмотреть на поверхность первой тарелки (которая находится на самом верху).

Взять верхнюю тарелку из стопки (обнажая таким образом следующую тарелку, которая находится под верхней, если она вообще существует).

Положить новую тарелку поверх стопки (спрятав под ней самую верхнюю тарелку, если она вообще была).

В компьютерном программировании стек представляет собой контейнер (как структуру данных), который содержит несколько переменных (подобно массиву). Однако, в то время как массив позволяет получить доступ и изменять элементы в любом порядке (так называемый «произвольный доступ»), стек более ограничен.

В стеке вы можете:

Посмотреть на верхний элемент стека (используя функцию top() или peek() ).

Вытянуть верхний элемент стека (используя функцию pop() ).

Добавить новый элемент поверх стека (используя функцию push() ).

Например, рассмотрим короткую последовательность, показывающую, как работает добавление и удаление в стеке:

Stack: empty
Push 1
Stack: 1
Push 2
Stack: 1 2
Push 3
Stack: 1 2 3
Push 4
Stack: 1 2 3 4
Pop
Stack: 1 2 3
Pop
Stack: 1 2
Pop
Stack: 1

Стопка тарелок довольно-таки хорошая аналогия работы стека, но есть лучшая аналогия. Например, рассмотрим несколько почтовых ящиков, которые расположены друг на друге. Каждый почтовый ящик может содержать только один элемент, и все почтовые ящики изначально пустые. Кроме того, каждый почтовый ящик прибивается гвоздем к почтовому ящику снизу, поэтому количество почтовых ящиков не может быть изменено. Если мы не можем изменить количество почтовых ящиков, то как мы получим поведение, подобное стеку?

Сегмент стека вызовов

Сегмент стека вызовов содержит память, используемую для стека вызовов. При запуске программы, функция main() помещается в стек вызовов операционной системой. Затем программа начинает свое выполнение.

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

Единственное отличие фактического стека вызовов от нашего гипотетического стека почтовых ящиков заключается в том, что, когда мы вытягиваем элемент из стека вызовов, нам не нужно очищать память (т.е. вынимать всё содержимое из почтового ящика). Мы можем просто оставить эту память для следующего элемента, который и перезапишет её. Поскольку указатель стека будет ниже этого адреса памяти, то, как мы уже знаем, эта ячейка памяти не будет находиться в стеке.

Стек вызовов на практике

Давайте рассмотрим детально, как работает стек вызовов. Ниже приведена последовательность шагов, выполняемых при вызове функции:

Программа сталкивается с вызовом функции.

Создается фрейм стека, который помещается в стек. Он состоит из:

адреса инструкции, который находится за вызовом функции (так называемый «обратный адрес»). Так процессор запоминает, куда ему возвращаться после выполнения функции;

памяти для локальных переменных;

сохраненных копий всех регистров, модифицированных функцией, которые необходимо будет восстановить после того, как функция завершит свое выполнение.

Процессор переходит к точке начала выполнения функции.

Инструкции внутри функции начинают выполняться.

После завершения функции, выполняются следующие шаги:

Регистры восстанавливаются из стека вызовов.

Фрейм стека вытягивается из стека. Освобождается память, которая была выделена для всех локальных переменных и аргументов.

Обрабатывается возвращаемое значение.

ЦП возобновляет выполнение кода (исходя из обратного адреса).

Возвращаемые значения могут обрабатываться разными способами, в зависимости от архитектуры компьютера. Некоторые архитектуры считают возвращаемое значение частью фрейма стека, другие используют регистры процессора.

Знать все детали работы стека вызовов не так уж и важно. Однако понимание того, что функции при вызове добавляются в стек, а при завершении выполнения — удаляются из стека, дает основы, необходимые для понимания рекурсии, а также некоторых других концепций, которые полезны при отладке программ.

Как происходит переполнение стека и как лучше всего этого избежать или как предотвратить его, особенно на веб-серверах, но были бы интересны и другие примеры?

ха-ха, у вас переполнение стека, и вы задаете вопрос «как предотвратить переполнение стека» :)

Стек в этом контексте является буфером «последним пришел - первым вышел», в который вы помещаете данные во время выполнения программы. Последний вошел - первым ушел (LIFO) означает, что последнее, что вы вставляете, всегда первое, что вы получаете обратно - если вы вставляете в стек 2 элемента, 'A', а затем 'B', то первое, что вы вставляете вне стека будет «B», а следующим будет «A».

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

Переполнение стека

Переполнение стека - это когда вы использовали больше памяти для стека, чем предполагалось использовать вашей программе. Во встроенных системах у вас может быть только 256 байтов для стека, и если каждая функция занимает 32 байта, тогда у вас может быть только вызовы функций глубиной 8 - функция 1 вызывает функцию 2, которая вызывает функцию 3, кто вызывает функцию 4 . кто вызывает функция 8, которая вызывает функцию 9, но функция 9 перезаписывает память вне стека. Это может привести к перезаписи памяти, кода и т. Д.

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

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

Обычно операционная система и язык программирования, который вы используете, управляют стеком, и это вне ваших рук. Вы должны посмотреть на свой граф вызовов (древовидную структуру, которая показывает из вашего main, что вызывает каждая функция), чтобы увидеть, насколько глубоко заходят ваши вызовы функций, и чтобы обнаружить циклы и рекурсию, которые не предназначены. Преднамеренные циклы и рекурсия необходимо искусственно проверять, чтобы исключить ошибки, если они вызывают друг друга слишком много раз.

Помимо хороших практик программирования, статического и динамического тестирования, на этих высокоуровневых системах мало что можно сделать.

Встроенные системы

Во встраиваемом мире, особенно в коде высокой надежности (автомобильный, авиационный, космический), вы выполняете обширный анализ и проверку кода, но вы также делаете следующее:

  • Запретить рекурсию и циклы - обеспечивается политикой и тестированием
  • Храните код и стек далеко друг от друга (код во флеш-памяти, стек в ОЗУ, и никогда не совпадать)
  • Поместите защитные полосы вокруг стека - пустую область памяти, которую вы заполняете магическим числом (обычно это команда программного прерывания, но здесь есть много вариантов), и сотни или тысячи раз в секунду вы смотрите на защитные полосы, чтобы убедиться, что они не были перезаписаны.
  • Использовать защиту памяти (т.е. не выполнять в стеке, не читать и не писать только вне стека)
  • Прерывания не вызывают вторичные функции - они устанавливают флаги, копируют данные и позволяют приложению позаботиться об их обработке (в противном случае вы можете получить 8 глубоко в дереве вызовов функций, иметь прерывание, а затем выйти из других функций внутри прерывание, вызывающее выброс). У вас есть несколько деревьев вызовов - одно для основных процессов и одно для каждого прерывания. Если ваши прерывания могут мешать друг другу . ну, драконы есть .

Языки и системы высокого уровня

Но на языках высокого уровня, работающих в операционных системах:

  • Уменьшите объем хранилища локальных переменных (локальные переменные хранятся в стеке - хотя компиляторы довольно умны в этом отношении и иногда помещают большие локальные переменные в кучу, если ваше дерево вызовов неглубокое)
  • Избегайте или строго ограничивайте рекурсию
  • Не разбивайте свои программы слишком далеко на все меньшие и меньшие функции - даже без подсчета локальных переменных каждый вызов функции потребляет до 64 байтов в стеке (32-битный процессор, экономия половины регистров ЦП, флаги и т. Д.)
  • Держите дерево вызовов неглубоким (аналогично приведенному выше утверждению)

Веб-серверы

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

Читайте также: