From Wikipedia, the free encyclopedia
In software, a stack overflow occurs if the call stack pointer exceeds the stack bound. The call stack may consist of a limited amount of address space, often determined at the start of the program. The size of the call stack depends on many factors, including the programming language, machine architecture, multi-threading, and amount of available memory. When a program attempts to use more space than is available on the call stack (that is, when it attempts to access memory beyond the call stack’s bounds, which is essentially a buffer overflow), the stack is said to overflow, typically resulting in a program crash.[1]
Causes[edit]
Infinite recursion[edit]
The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.[2]
An example of infinite recursion in C.
int foo() { return foo(); }
The function foo, when it is invoked, continues to invoke itself, allocating additional space on the stack each time, until the stack overflows resulting in a segmentation fault.[2] However, some compilers implement tail-call optimization, allowing infinite recursion of a specific sort—tail recursion—to occur without stack overflow. This works because tail-recursion calls do not take up additional stack space.[3]
Some C compiler options will effectively enable tail-call optimization; for example, compiling the above simple program using gcc with -O1
will result in a segmentation fault, but not when using -O2
or -O3
, since these optimization levels imply the -foptimize-sibling-calls
compiler option.[4] Other languages, such as Scheme, require all implementations to include tail-recursion as part of the language standard.[5]
Very deep recursion[edit]
A recursive function that terminates in theory but causes a call stack buffer overflow in practice can be fixed by transforming the recursion into a loop and storing the function arguments in an explicit stack (rather than the implicit use of the call stack). This is always possible because the class of primitive recursive functions is equivalent to the class of LOOP computable functions. Consider this example in C++-like pseudocode:
void function (argument) { if (condition) function (argument.next); } |
stack.push(argument); while (!stack.empty()) { argument = stack.pop(); if (condition) stack.push(argument.next); } |
A primitive recursive function like the one on the left side can always be transformed into a loop like on the right side.
A function like the example above on the left would not be a problem in an environment supporting tail-call optimization; however, it is still possible to create a recursive function that may result in a stack overflow in these languages. Consider the example below of two simple integer exponentiation functions.
int pow(int base, int exp) { if (exp > 0) return base * pow(base, exp - 1); else return 1; } |
int pow(int base, int exp) { return pow_accum(base, exp, 1); } int pow_accum(int base, int exp, int accum) { if (exp > 0) return pow_accum(base, exp - 1, accum * base); else return accum; } |
Both pow(base, exp)
functions above compute an equivalent result, however, the one on the left is prone to causing a stack overflow because tail-call optimization is not possible for this function. During execution, the stack for these functions will look like this:
pow(5, 4) 5 * pow(5, 3) 5 * (5 * pow(5, 2)) 5 * (5 * (5 * pow(5, 1))) 5 * (5 * (5 * (5 * pow(5, 0)))) 5 * (5 * (5 * (5 * 1))) 625 |
pow(5, 4) pow_accum(5, 4, 1) pow_accum(5, 3, 5) pow_accum(5, 2, 25) pow_accum(5, 1, 125) pow_accum(5, 0, 625) 625 |
Notice that the function on the left must store in its stack exp
number of integers, which will be multiplied when the recursion terminates and the function returns 1. In contrast, the function at the right must only store 3 integers at any time, and computes an intermediary result which is passed to its following invocation. As no other information outside of the current function invocation must be stored, a tail-recursion optimizer can «drop» the prior stack frames, eliminating the possibility of a stack overflow.
Very large stack variables[edit]
The other major cause of a stack overflow results from an attempt to allocate more memory on the stack than will fit, for example by creating local array variables that are too large. For this reason some authors recommend that arrays larger than a few kilobytes should be allocated dynamically instead of as a local variable.[6]
An example of a very large stack variable in C:
int foo() { double x[1048576]; }
On a C implementation with 8 byte double-precision floats, the declared array consumes 8 megabytes of data; if this is more memory than is available on the stack (as set by thread creation parameters or operating system limits), a stack overflow will occur.
Stack overflows are made worse by anything that reduces the effective stack size of a given program. For example, the same program being run without multiple threads might work fine, but as soon as multi-threading is enabled the program will crash. This is because most programs with threads have less stack space per thread than a program with no threading support. Because kernels are generally multi-threaded, people new to kernel development are usually discouraged from using recursive algorithms or large stack buffers.[7]
See also[edit]
- Buffer overflow
- Call stack
- Heap overflow
- Stack buffer overflow
- Double fault
References[edit]
- ^ Burley, James Craig (1991-06-01). «Using and Porting GNU Fortran». Archived from the original on 2012-02-06.
- ^ a b What is the difference between a segmentation fault and a stack overflow? Archived 2021-09-13 at the Wayback Machine at Stack Overflow
- ^ «An Introduction to Scheme and its Implementation». 1997-02-19. Archived from the original on 2007-08-10.
- ^ «Using the GNU Compiler Collection (GCC): Optimize Options». Archived from the original on 2017-08-20. Retrieved 2017-08-20.
- ^ Richard Kelsey; William Clinger; Jonathan Rees; et al. (August 1998). «Revised5 Report on the Algorithmic Language Scheme». Higher-Order and Symbolic Computation. 11 (1): 7–105. doi:10.1023/A:1010051815785. S2CID 14069423. Archived from the original on 2007-01-05. Retrieved 2012-08-09.
- ^ Feldman, Howard (2005-11-23). «Modern Memory Management, Part 2». Archived from the original on 2012-09-20. Retrieved 2007-08-14.
- ^ «Kernel Programming Guide: Performance and Stability Tips». Apple Inc. 2014-05-02. Archived from the original on 2014-05-03. Retrieved 2014-05-02.
External links[edit]
- The reasons why 64-bit programs require more stack memory Archived 2017 November 4 at the Wayback Machine
This is a typical case of java.lang.StackOverflowError
… The method is recursively calling itself with no exit in doubleValue()
, floatValue()
, etc.
File Rational.java
public class Rational extends Number implements Comparable<Rational> {
private int num;
private int denom;
public Rational(int num, int denom) {
this.num = num;
this.denom = denom;
}
public int compareTo(Rational r) {
if ((num / denom) - (r.num / r.denom) > 0) {
return +1;
} else if ((num / denom) - (r.num / r.denom) < 0) {
return -1;
}
return 0;
}
public Rational add(Rational r) {
return new Rational(num + r.num, denom + r.denom);
}
public Rational sub(Rational r) {
return new Rational(num - r.num, denom - r.denom);
}
public Rational mul(Rational r) {
return new Rational(num * r.num, denom * r.denom);
}
public Rational div(Rational r) {
return new Rational(num * r.denom, denom * r.num);
}
public int gcd(Rational r) {
int i = 1;
while (i != 0) {
i = denom % r.denom;
denom = r.denom;
r.denom = i;
}
return denom;
}
public String toString() {
String a = num + "/" + denom;
return a;
}
public double doubleValue() {
return (double) doubleValue();
}
public float floatValue() {
return (float) floatValue();
}
public int intValue() {
return (int) intValue();
}
public long longValue() {
return (long) longValue();
}
}
File Main.java
public class Main {
public static void main(String[] args) {
Rational a = new Rational(2, 4);
Rational b = new Rational(2, 6);
System.out.println(a + " + " + b + " = " + a.add(b));
System.out.println(a + " - " + b + " = " + a.sub(b));
System.out.println(a + " * " + b + " = " + a.mul(b));
System.out.println(a + " / " + b + " = " + a.div(b));
Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
new Rational(5, 1), new Rational(4, 1),
new Rational(3, 1), new Rational(2, 1),
new Rational(1, 1), new Rational(1, 2),
new Rational(1, 3), new Rational(1, 4),
new Rational(1, 5), new Rational(1, 6),
new Rational(1, 7), new Rational(1, 8),
new Rational(1, 9), new Rational(0, 1)};
selectSort(arr);
for (int i = 0; i < arr.length - 1; ++i) {
if (arr[i].compareTo(arr[i + 1]) > 0) {
System.exit(1);
}
}
Number n = new Rational(3, 2);
System.out.println(n.doubleValue());
System.out.println(n.floatValue());
System.out.println(n.intValue());
System.out.println(n.longValue());
}
public static <T extends Comparable<? super T>> void selectSort(T[] array) {
T temp;
int mini;
for (int i = 0; i < array.length - 1; ++i) {
mini = i;
for (int j = i + 1; j < array.length; ++j) {
if (array[j].compareTo(array[mini]) < 0) {
mini = j;
}
}
if (i != mini) {
temp = array[i];
array[i] = array[mini];
array[mini] = temp;
}
}
}
}
Result
2/4 + 2/6 = 4/10
Exception in thread "main" java.lang.StackOverflowError
2/4 - 2/6 = 0/-2
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 * 2/6 = 4/24
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 / 2/6 = 12/8
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
Here is the source code of StackOverflowError
in OpenJDK 7.
Содержание:
1. Что такое стек в конфигурации программы 1С?
2. Для чего нужен стек и откуда появляется ошибка переполнения стека?
3. Причины, вызывающие переполнение стека в системе программы 1С: Предприятие
1. Что такое стек в конфигурации программы 1С?
При работе в конфигурации системы 1С, после обновления конфигурации или платформы, или по каким-то другим причинам, все мы сталкиваемся с различными всплывающими ошибками. Разберемся сегодня, что можно сделать, если столкнуться с ошибкой – «Переполнение стека встроенного языка на сервере». Разберемся, что вызывает эту ошибку:
Переполнение стека возникает, когда в стек помещается больше элементов, чем он может вместить.
Стек – это по сути стопка элементов, в которой эти элементы «укладываются» один поверх другого. Работает этот механизм по принципу LIFO (Last In, First Out – последним вошел/первым вышел). Поэтому добраться мы можем в первую очередь до того элемента, который положили последним, потом предпоследним, и т.д.
2. Для чего нужен стек и откуда появляется ошибка переполнения стека?
Главное назначение стека — чтобы организовать команду вызова подпрограмм удобным образом. Например, вызывается «функция1» с определенными переменными, и которая при этом передает определенные параметры и вызывает «функцию2» со своими переменными. В свою очередь «функция2» передает свои параметры и вызывает «функцию3», и т.д. Все эти параметры и переменные нужно сохранять (куда-то складывать), и затем по порядку вызывать. Для этого как раз удобно использовать стек.
Если рассматривать стек абстрактно – это «безразмерный склад» для 1С хранения информации. Но фактически – это не так.
Появляется подобная ошибка именно от того, что стек по своей сути имеет границы, и его объем, а точнее память, выделенная под стек, ограничена. И поскольку стек организует команду вызова функций, при его переполнении функции уже не могут вызываться.
3. Причины, вызывающие переполнение стека в системе программы 1С: Предприятие
1. Самый распространенный вариант – это рекурсия. И если происходит бесконечная рекурсия, то прерывается она таким образом.
Выглядит это условно так:
Процедура ИмяПроцедурыВызываемойРекурсивно()
…
ИмяПроцедурыВызываемойРекурсивно();
…
КонецПроцедуры
2. Локальные переменные, которые требуют себе большого объема оперативной памяти. Это может быть, например, массив из какого-нибудь миллиона локальных переменных или элементов. Вызывая функцию с таким массивом, можно легко получить в ответ переполнение стека.
Переполнение стека — фатальная ошибка, чаще всего встречающаяся в программах, программный код 1С, которых имеет рекурсивные функции. Либо, даже если вы не используете рекурсию, программа содержит множество локальных переменных или есть ошибки в ручном выделении в стеке оперативной памяти. Актуальны все классические правила:
1. Если есть возможность – не используем в работе рекурсию.
2. Не выполнять вручную ту работу, которую может выполнить компилятор.
Специалист компании «Кодерлайн»
Марина Анапольская
Стек (или магазин) — структура данных в программировании, работающая по принципу магазина с патронами: последний помещеннный в него объект, обрабатывается первым.
Приработе со стекам часто приходится сталкиваться с двумя типичными ошибками: переполненем стека и опустошением стека.
Переполнение стека (stack overflow) — одна из типичных ошибок при работе со стеком, состоящая в попытке добавить в стек элемент, когда память, отведенная для хранения стека полностью занята.
В случае, если стек моделируется на базе массива, добавляемое значение может быть записано за пределы отведенного для хранения стека памяти, что приведет к повреждению другие данных, обрабатываемых программой. Это нередко приводит к трудно выявляемым ошибками типа «порчи памяти». Поэтому при реализации стека на основе массива необходимо перед каждой операцией добавления элемента проверять, не переполнен ли стек.
Если стек моделируется на связанном списке, то переполнение стека обычно возникает только при исчерпании доступной для программы оперативной памяти. В этом случае программа завершается с диагностикой «Недостаточно памяти».
Причиной переполнения стека обычно является зацикливание на участке программы, где количество операций добавления в стек превышает количество операций извлечения из стека. Другая причина переполнения стека — слишком большая глубина рекурсивных вызовов подпрограмм, что может говорить о неудачно выбранном алгоритме решения задачи.
Опустошение стека (stack underflow) — другая типичная ошибка при работе со стеком, состоящая в попытке извлечь значение пустого стека.
В случае, если стек моделируется на базе массива, то при его опустошении в качестве результата операции может быть возвращено случайное («мусорное») значение из области памяти, не отведенной для хранения стека. Это скорее всего приведет к неверной работе программы. Кроме того, при попытке пополнить стек после его ошибочного опустошения, данные могут быть записаны в постороннюю область памяти, что приведет к тем же непредсказуемым последствиям, что и переполнение стека.
Если стек моделируется на связанном списке, то ошибка опустошения стека выражается в попытке обращения по недействительному указателю. Обычно это немедленно приводит к завершению программы с диагностикой «защита памяти».
Причиной опустошения стека обычно является зацикливание на участке программы, где количество операций извлечения из стека превышает количество операций добавления в стек. Другая причина переполнения стека — несогласованность операций пополнения и извлечения из стека. Например, если подпрограмма ожидает получить больше параметров, чем ей передается при вызове через стек.
Чтобы избегать ошибок при работе со стеком нужно следовать двум правилам.
1. При реализации операций со стеком всегда проверять, не приведет ли затребованное действие к переполнению или опустошению стека. Если нарушение обнаружено, то выдавать соответствующую диагностику и отказывать в выполнении операции.
2. При каждой операции использования стека проверять успешность ее, выполнения и в случае возникновения ошибки принимать соответствующие меры.
Описанные правила безопасности приводят к тому, что программный код оказывается перегружен проверками. По этой причине более эффективным методом является уведомление об ошибках в работе со стеком при помощи механизма исключений или прерываний (например, конструкция try … throw в языке С++).
Дополнительно в базе данных Генона:
- Что такое стек в программировании?
- Для чего используются указатели в программировании?
Ссылки по теме:
- cyberforum.ru — реализация стека на С++ с использованием исключений
- ru.wikipedia.org — Википедия: Переполнение буфера
- codenet.ru — рассматривается уязвимость Windows за счет использования переполнения стека
- sdteam.com — статья «Переполнения стека»
- xakep.ru — статья «Переполнение буфера в стеке», Хакер, №2, 2003
В мире программистов ошибка «stack overflow» очень известна благодаря тому, что этот вид ошибки довольно распространен. А сам термин «stack overflow» известен еще больше, чем ошибка, благодаря одноименному англоязычному ресурсу «StackOverflow». Это сообщество программистов международного масштаба, и еще куча всего интересного. Поэтому не нужно путать ошибку «stack overflow» и веб-ресурс с таким же названием. В нашей статье речь пойдет об ошибке.
Ошибка «stack overflow» связана с переполнением стека. Она появляется в том случае, когда в стеке должно сохраниться больше информации, чем он может уместить. Объем памяти стека задает программист при запуске программы. Если в процессе выполнения программы стек переполняется, тогда возникает ошибка «stack overflow» и программа аварийно завершает работу. Причин возникновения подобной ошибки может быть несколько.
Ошибка «stack overflow»
Нужно отметить, что ошибка «stack overflow» не связана с конкретным языком программирования, то есть она может возникнуть в программах на Java, C++, C, C# и других компилируемых языках.
Причин ее возникновения может быть несколько. К самым распространенным причинам относятся:
бесконечная рекурсия;
глубокая рекурсия;
проблемы с переменными в стеке.
Бесконечная рекурсия и ошибка «stack overflow»
Бесконечная рекурсия редко возникает самостоятельно и по неизвестным причинам. Обычно программист:
забывает прописывать условие для выхода из рекурсии;
пишет неосознанную косвенную рекурсию.
Самая частая причина из категории «бесконечной рекурсии» — программист забывает прописывать условия выхода или же прописывает, но условия выхода не срабатывают.
Вот как это выглядит на С:
int factorial (int number)
{
if (number == 0)
return 1;
return number * factorial(number — 1);
}
В описанном примере прописаны условия выхода из рекурсии, однако они никогда не сработают, если «number» будет отрицательным. Поэтому через несколько миллионов вызовов стек будет переполнен и возникнет ошибка «stack overflow». В некоторых языках программирования предусмотрена «защита» от таких рекурсий. В них рекурсия из конца функции конвертируется в цикл, что не будет расходовать стековую память. Но подобная «оптимизация» вызовет другую, менее опасную проблему — «зацикливание».
Неосознанная бесконечная рекурсия возникает в том случае, если программист по невнимательности распределяет один и тот же функционал программы между разными нагруженными функциями, а потом делает так, что они друг друга вызывают.
В коде это выглядит так:
int Object::getNumber(int index, bool& isChangeable)
{
isChangeable = true;
return getNumber(index);
}
int Object::getNumber(int index)
{
bool noValue;
return getNumber(index, noValue);
}
Глубокая рекурсия и ошибка «stack overflow»
Глубокая рекурсия — это такая рекурсия, которая имеет свое окончание через определенное время, поэтому она не бесконечная. Однако памяти стека не хватит для завершения такой рекурсии, поэтому ошибка «stack overflow» обеспечена. Обычно такая ситуация заранее просчитывается программистом,поэтому ее можно решить. Например, можно:
отыскать другой программный алгоритм для решения поставленной задачи, чтобы избежать применения рекурсии;
«вынести» рекурсию за пределы аппаратного стека в динамический;
и другое.
Глубокая рекурсия выглядит так:
void eliminateList(struct Item* that)
{
if (that == NULL)
return;
eliminateList(that->next);
free(that);
}
Проблемы с переменными в стеке и ошибка «stack overflow»
Если взглянуть на популярность возникновения «stack overflow error», то причина с проблемными переменными в стеке стоит на первом месте. Кроется она в том, что программист изначально выделяет слишком много памяти локальной переменной.
Например:
int function() {
double b[1000000]
}
В данном случае может возникнуть такая ситуация, что массиву потребуется объем памяти, который стек не способен будет обеспечить, а значит, возникнет ошибка «stack overflow».
Заключение
Ошибка «stack overflow» возникает довольно часто. Каждый конкретный случай ее возникновения требует собственного решения. Одна причина объединяет возникновение такой ошибки — невнимательность программиста. Если «stack overflow error» возникла, значит, программист где-то что-то упустил или не доглядел.