Стек: что это такое и применение

О стеке простыми словами — для студентов и просто начинающих

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

На Википедии определение стека звучит так:

Стек (англ. stack — стопка; читается стэк) — абстрактный тип данных, представляющий собой список элементов, организованных по принципу LIFO (англ. last in — first out, «последним пришёл — первым вышел»).

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

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

Итак, из чего же состоит стек.

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

На данной картинке схематично изображен стек. Блок вида «Данные/*next» и есть наша ячейка. *next, как мы видим, указывает на следующий элемент, другими словами указатель *next хранит адрес следующей ячейки. Указатель *TOP указывает на вершину стек, то есть хранит её адрес.

С теорией закончили, перейдем к практике.

На заре начала: процессор, память и стек

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

  • адрес = значение;
  • индекс = значение.

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

Стопка тарелок — традиционная новелла о сути стека: понятие stack и перевод в общебытовом сознании. Нельзя взять тарелку снизу, можно брать только сверху, и тогда все тарелки будут целы.

stack перевод

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

Как устроен стек

Как устроен стек

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

В стеке важна последовательность данных и здесь применима линейная связь:

Данные следуют друг за другом, брать их из произвольного места нельзя.

Добавление или проталкивание (на английском — push) элемента возможно только в вершину стека. Как только элемент стека использован, он удаляется (процесс называется выталкиванием, или pop на английском), а верхним элементом (top) становится следующий.

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

Похожим образом реализована другая структура данных — очередь (queue на английском). Но разница лишь в том, что в очереди первым используется раньше всех попавший в нее элемент, а последним — тот, что позже всех. Для наглядности представьте очередь на кассе супермаркета: кто первым занял место, тот первым и расплатился. Все, как и в реальной жизни.

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

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

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

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

Зачем все это нужно?

Вы вряд ли сможете написать программу, которая не будет использовать функции (подпрограммы). При вызове функции в стек копируется адрес для возврата после окончания выполнения данной подпрограммы. По окончании её выполнения адрес возвращается из стека в счетчик команд и программа продолжает выполняться с места после функции.
Также в стек необходимо помещать регистры, которые используются в данной подпрограмме (в языках высокого уровня этим занимается компилятор).
Все вышесказанное характерно для так называемого аппаратного стека. Надеюсь вы догадываетесь, что такая структура данных (LIFO — last in, first out) полезна далеко не только при работе на низком уровне. Часто возникает необходимость хранить данные в таком порядке (например известный алгоритм разбора арифметических выражений основан на работе со стеком), тогда программисты реализуют программный стек.

Давайте разберем работу со стеком на примере контроллеров семейства MSP430. Я выбрал их только из-за того что у меня оказалась установленной среда для работы с ними.
В MSP430 стек основан на предекрементной схеме. Т.е. перед тем как вы записываете данные в стек он уменьшает адрес вершины стека (верхней тарелки). Бывает также постдекрементный/постинкрементный (вычитание/добавление вершины стека происходит после записи данных) и прединкрементный (перед записью адрес вершины увеличивается).
Если стек увеличивает свой адрес при записи данных, говорят о стеке растущем вверх, если же уменьшает — вниз.
За хранения адреса вершины стека отвечает регистр SP.

Регистры MSP430

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

Рассмотрим вот такую программу:

Реализации [ править ]

Для стека с [math]n[/math] элементами требуется [math]O(n)[/math] памяти, так как она нужна лишь для хранения самих элементов.

Перед реализацией стека выделим ключевые поля:

  • [math]mathtt [/math] — массив, с помощью которого реализуется стек, способный вместить не более [math]n[/math] элементов,
  • [math]mathtt[/math] — индекс последнего помещенного в стек элемента.

Стек состоит из элементов [math]mathtt [/math] , где [math]mathtt[/math] — элемент на дне стека, а [math]mathtt[/math] — элемент на его вершине. Если [math]mathtt[/math] , то стек не содержит ни одного элемента и является пустым (англ. empty). Протестировать стек на наличие в нем элементов можно с помощью операции — запроса [math] mathtt [/math] . Если элемент снимается с пустого стека, говорят, что он опустошается (англ. underflow), что обычно приводит к ошибке. Если значение [math]mathtt[/math] больше [math]mathtt[/math] , то стек переполняется (англ. overflow). (В представленном ниже псевдокоде возможное переполнение во внимание не принимается.)

Каждую операцию над стеком можно легко реализовать несколькими строками кода:

Как видно из псевдокода выше, все операции со стеком выполняются за [math]O(1)[/math] .

Что такое стек?

Стек — список элементов, который может быть изменён лишь с одной стороны, называющейся вершиной стека.

Представьте приспособление для раздачи тарелок, в котором тарелки стоят в стопке. Новые тарелки можно добавлять только поверх уже имеющихся, а брать можно лишь сверху. Таким образом, чем позже тарелку положат в стопку, тем раньше её оттуда возьмут. В рамках структур данных это называется LIFO-принципом (последним пришёл — первым ушёл).

Если использовать терминологию, то стек поддерживает операции добавления (push) и удаления (pop) элементов на его вершине.

Заключение

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

Как видим, между этими двумя реализациями стека практически нет различий — используйте ту, что нравится вам.

Adblock
detector