Stack
В этом контексте стек является последним входным, первым выходным буфером, в который вы помещаете данные во время работы вашей программы. «Последний пришел, первый вышел» (LIFO) означает, что последнее, что вы вставляете, это всегда первое, что вы возвращаете - если вы помещаете 2 стека в стек, «А», а затем «В», то первое, что вы вставляете со стека будет «B», а затем «A».
Когда вы вызываете функцию в своем коде, следующая инструкция после вызова функции сохраняется в стеке, и любое пространство памяти, которое может быть перезаписано вызовом функции. Вызываемая функция может использовать больше стека для своих локальных переменных. Когда это сделано, оно освобождает используемое пространство стека локальной переменной, а затем возвращается к предыдущей функции.
Переполнение стека
Переполнение стека - это когда вы используете больше памяти для стека, чем ваша программа должна была использовать. Во встроенных системах у вас может быть только 256 байтов для стека, и если каждая функция занимает 32 байта, то вы можете иметь только вызовы функций 8: глубокая функция 1 вызывает функцию 2, которая вызывает функцию 3, которая вызывает функцию 4 .... кто вызывает функция 8, которая вызывает функцию 9, но функция 9 перезаписывает память вне стека. Это может перезаписать память, код и т. Д.
Многие программисты совершают эту ошибку, вызывая функцию A, которая затем вызывает функцию B, которая затем вызывает функцию C, которая затем вызывает функцию A. Это может работать большую часть времени, но только один раз неправильный ввод вызовет его этот круг навсегда, пока компьютер не распознает, что стек переполнен.
Рекурсивные функции также являются причиной этого, но если вы пишете рекурсивно (т. Е. Ваша функция вызывает себя), вам нужно знать об этом и использовать статические / глобальные переменные для предотвращения бесконечной рекурсии.
Как правило, ОС и используемый вами язык программирования управляют стеком, и это не в ваших руках. Вы должны взглянуть на свой график вызовов (древовидную структуру, которая показывает из основного, что вызывает каждая функция), чтобы увидеть, насколько глубоки ваши вызовы функций, а также обнаружить циклы и рекурсию, которые не предназначены. Преднамеренные циклы и рекурсия должны быть искусственно проверены на наличие ошибок, если они вызывают друг друга слишком много раз.
Помимо хороших методов программирования, статического и динамического тестирования, в этих системах высокого уровня мало что можно сделать.
Встроенные системы
Во встроенном мире, особенно в коде высокой надежности (автомобильный, авиационный, космический), вы проводите подробные обзоры и проверки кода, но также делаете следующее:
- Запретить рекурсию и циклы - обеспечивается политикой и тестированием
- Держите код и стеки далеко друг от друга (код во флэш-памяти, стек в ОЗУ и никогда не встречайте)
- Поместите защитные полосы вокруг стека - пустую область памяти, которую вы заполняете магическим числом (обычно это инструкция программного прерывания, но здесь много вариантов), и сотни или тысячи раз в секунду вы смотрите на защитные полосы чтобы убедиться, что они не были перезаписаны.
- Использовать защиту памяти (т. Е. Не выполнять в стеке, не выполнять чтение или запись только вне стека)
- Прерывания не вызывают вторичные функции - они устанавливают флаги, копируют данные и позволяют приложению позаботиться об их обработке (в противном случае вы можете получить 8 в глубине дерева вызовов функций, иметь прерывание, а затем выйти еще несколько раз). функционирует внутри прерывания, вызывая выброс). У вас есть несколько деревьев вызовов - одно для основных процессов и одно для каждого прерывания. Если ваши прерывания могут прервать друг друга ... ну, есть драконы ...
Языки и системы высокого уровня
Но на языках высокого уровня работают в операционных системах:
- Уменьшите хранилище локальных переменных (локальные переменные хранятся в стеке - хотя компиляторы достаточно умны в этом и иногда помещают большие локальные ресурсы в кучу, если дерево вызовов мелкое)
- Избегать или строго ограничивать рекурсию
- Не разбивайте свои программы слишком далеко на мелкие и мелкие функции - даже без учета локальных переменных каждый вызов функции занимает до 64 байт в стеке (32-битный процессор, сохраняя половину регистров ЦП, флагов и т. Д.)
- Держите дерево вызовов неглубоким (аналогично приведенному выше утверждению)
Веб-серверы
Это зависит от имеющейся у вас «песочницы», можете ли вы контролировать или даже видеть стек. Скорее всего, вы можете обращаться с веб-серверами так же, как с любым другим языком и операционной системой высокого уровня - это в значительной степени не в ваших руках, но проверьте язык и стек, который вы используете. Например, можно создать стек на вашем сервере SQL.
-Adam