Стек

Последнее обновление: 01.03.2024

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

Стек и LIFO

Последний добавленный в стек объект представляет верхушку стека (top of stack или tos). И если нам надо взять из стека данные, то сначала мы берем их из верхушки стека.

Для управления стеком обычно выделается отдельный регистр, который еще называется указателем стека. В архитектуре Intel x86-64 в качестве такого регистра выступает RSP, на ARM - регистр SP

Когда программа начинает выполняться, программе выделяется стек в виде области в оперативной памяти обычно в несколько мегабайт. При этом операционная система инициализирует регистр RSP/SP адресом непосредственно перед началом области стека. В последствии указатель стека RSP/SP указывает на верхушку стека - адрес последнего добавленного значения.

Стек растет от больших адресов к меньшим, то есть при добавлении в стек данных, адрес добавляемых данных будет уменьшаться на размер добавляемых данных. А при получении данных, адрес, наоборот, увеличивается на размер полученных данных. Например, определим следующую программу на языке Python:

sp = 8            # указатель стека
stack = [0]*sp      # условный стек

# функция лобавления в стек
def push(data):
    global sp
    if sp==0: 
        raise Exception("Переполнение стека")
    sp = sp - 1         # уменьшаем указатель стека. стек указывает на следующее свободное место
    stack[sp] = data   # помещаем в стек данные
    print("stack:", stack, "\tsp: ", sp)
    
# функция извлечения из стека
def pop():
    global sp
    if(sp >= len(stack)): raise Exception("Stack is empty")  # если стек пуст
    data = stack[sp]    # получаем данные
    sp = sp + 1         # увеличиваем адрес в стеке
    print("stack:", stack, "\tsp: ", sp)
    return data

# помещаем в стек данные
push(1)
push(2)
push(3)

# получаем данные. tos - top of the stack - верхушка стека
tos = pop()
print("tos:", tos)
tos = pop()
print("tos:", tos)
tos = pop()
print("tos:", tos)

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

sp = 8            # указатель стека
stack = [0]*sp      # условный стек

Для определения стека используем список из 8 элементов. В начале программы указатель стека указывает на условный адрес после последней ячейки в стеке. То есть у нас ячейки в стеке имеют условные адреса 0, 1, 2, ...7. Соответственно указатель стека равен 8.

В функции push через параметр получаем данные, уменьшаем указатель стека и по адресу, на который он ссылается, помещаем переданные данные:

sp = sp - 1         # уменьшаем указатель стека. стек указывает на следующее свободное место
stack[sp] = data   # помещаем в стек данные

Если стек уже заполнен, генерируем исключение.

В функции pop получаем данные и увеличиваем адрес в указателе стека:

data = stack[sp]    # получаем данные
sp = sp + 1         # увеличиваем адрес в стеке

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

Для теста в программе помещаем в стек 3 числа и потом их извлекаем.

# помещаем в стек данные
push(1)
push(2)
push(3)

# получаем данные
tos = pop()
print("tos:", tos)
tos = pop()
print("tos:", tos)
tos = pop()
print("tos:", tos)

После добавления числа числа 3 стек приобретет следующий вид

АдресЗначение
0x00000
0x00010
0x00020
0x00030
0x00040
0x00053SP=5
0x00062
0x00071

В результате программа даст следующий консольный вывод:

stack: [0, 0, 0, 0, 0, 0, 0, 1] 	sp:  7
stack: [0, 0, 0, 0, 0, 0, 2, 1] 	sp:  6
stack: [0, 0, 0, 0, 0, 3, 2, 1] 	sp:  5
stack: [0, 0, 0, 0, 0, 3, 2, 1] 	sp:  6
tos: 3
stack: [0, 0, 0, 0, 0, 3, 2, 1] 	sp:  7
tos: 2
stack: [0, 0, 0, 0, 0, 3, 2, 1] 	sp:  8
tos: 1

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

push(1)
print("tos:", pop())     # tos: 1
push(22)
print("tos:", pop())     # tos: 22

Результат программы:

stack: [0, 0, 0, 0, 0, 0, 0, 1] 	sp:  7
stack: [0, 0, 0, 0, 0, 0, 0, 1] 	sp:  8
tos: 1
stack: [0, 0, 0, 0, 0, 0, 0, 22] 	sp:  7
stack: [0, 0, 0, 0, 0, 0, 0, 22] 	sp:  8
tos: 22
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850