Stack(스택)은 다양한 프로그래밍 환경에서 반복적으로 등장하는 기본 자료구조입니다. 동작 방식은 단순하지만 실제 시스템 구조 속에서 핵심적인 역할을 담당합니다. 가장 중요한 특징은 LIFO(Last-In, First-Out), 즉, 마지막에 들어간 게 가장 먼저 나온다는 점입니다.
이번 글에서는 스택의 개념과 특징을 정리하고, C# 배열을 이용해 직접 구현하는 방법을 살펴보겠습니다.
1. 스택 구조와 동작 방식
스택은 한쪽 끝에서만 데이터가 삽입되고 제거되는 구조입니다. 삽입(push)과 제거(pop)는 모두 “top” 위치에서 발생하며, 중간 데이터에는 직접 접근하지 않습니다.
“박스 쌓기”에 비유하면 이해가 쉽습니다.
- A 올려놓음
- B 올려놓음
- C 올려놓음
→ 꺼낼 때는 C → B → A 순서로 꺼냄

※ 동작을 시각적으로 확인해보기
아래 링크는 스택(Stack)의 Push / Pop 동작을 단계별로 시각화해 주는 도구입니다. 함께 참고하면 데이터가 어떻게 쌓이고 제거되는지 직관적으로 이해할 수 있습니다.
https://www.cs.usfca.edu/~galles/visualization/StackArray.html
2. 스택이 활용되는 대표적인 사례
스택은 구조가 단순하지만 다음과 같은 기능의 핵심 요소로 활용됩니다.
- 되돌리기(Undo) 기능
이전 작업 상태를 스택에 저장해 두었다가 하나씩 꺼내며 복원합니다. - 함수 호출/리턴 구조(Call Stack)
함수 호출은 스택 프레임을 쌓아가는 방식으로 처리되며, 리턴 시 역순으로 제거됩니다. - 깊이 우선 탐색(DFS)
방문 경로를 스택에 저장하고, 가장 최근 노드를 기준으로 탐색을 확장합니다. - 문자열 괄호 검증
여는 괄호는 push, 닫는 괄호는 pop 하여 올바른 괄호 구조를 검사합니다.
스택의 LIFO 구조는 문제 해결 방식과 자연스럽게 맞물리기 때문에 다양한 알고리즘에서 강력한 도구로 사용됩니다.
3. 스택이 제공하는 기능 (구현 기준)
배열 기반 스택은 다음과 같은 핵심 연산을 제공합니다.
- Push : 데이터를 top 위치에 삽입
- Pop : top 위치의 데이터를 제거하며 반환
- Peek : top 위치 데이터 조회(제거하지 않음)
- Count : 저장된 데이터 수
- IsEmpty : 스택이 비어 있는지 여부 (선택)
※ 배열 기반 구현에서는 삽입 중 배열 용량이 부족하면 동적으로 확장해야 합니다.
4. 스택의 내부 동작 방식
스택은 내부적으로 배열 하나와 현재 쌓여 있는 위치를 나타내는 top 인덱스로 구현할 수 있습니다.
아래 그림을 통해, Push와 Pop이 코드 상에서 어떻게 처리되는지 순서대로 살펴보겠습니다.
✔ 초기 상태
- top = 0
- 아직 아무 값도 없습니다.

✔ A 추가 (Push)
- 현재 top 위치(배열[0])에 "A" 저장
- 이후 top을 1 증가시켜 다음 삽입 위치를 준비합니다.

✔ B, C 추가 (Push)
- 같은 방식으로 "B", "C"가 순서대로 쌓이고
- 삽입할 때마다 top은 하나씩 증가합니다.
→ 마지막에 top = 3

✔ C 꺼내기 (Pop)
- 먼저 top을 1 감소시키고 (3 → 2)
- 감소된 위치(배열[2])의 값을 꺼냅니다.
- 꺼낸 자리의 참조를 초기화하여 정리합니다.

5. C# 배열로 스택 직접 구현하기
아래 코드는 배열을 기반으로 스택을 제네릭 형태로 구현한 예제입니다.
using System;
namespace Daebak.Common.Collections
{
public class Stack<T>
{
private T[] _items; // 데이타가 저장될 배열
private int _top; // top 위치
public int Count => _top; // 전체 데이타 개수
public Stack(int capacity = 10)
{
_items = new T[capacity];
_top = 0;
}
public void Push(T item)
{
if (_top >= _items.Length)
{
// 배열 크기를 두 배로 늘리고, 기존 데이타를 복사
Array.Resize(ref _items, _items.Length * 2);
}
// 현재 _top 위치에 데이타를 추가하고, _top을 증가시킴
_items[_top] = item;
++_top;
}
public T Pop()
{
if (_top == 0)
{
throw new InvalidOperationException("스택이 비어 있습니다.");
}
// _top을 감소시키고, 해당 위치의 데이타를 반환
--_top;
T item = _items[_top];
_items[_top] = default; // 메모리 누수를 방지하기 위해 참조를 제거
return item;
}
public T Peek()
{
if (_top == 0)
{
throw new InvalidOperationException("스택이 비어 있습니다.");
}
// 스택의 맨 위에 있는 데이타를 반환합니다. (데이타를 제거하지 않음)
return _items[_top - 1];
}
public bool IsEmpty()
{
return (_top == 0);
}
}
}
- Push 메서드에서 배열의 용량이 부족해지면 Array.Resize(ref _items, _items.Length * 2)를 사용하여 내부 배열의 크기를 두 배로 늘려 공간을 확보합니다.
6. 유니티(Unity) 환경에서 테스트해 보기
Unity 프로젝트에서 구현한 스택이 정상적으로 동작하는지 확인하려면 다음과 같은 테스트 스크립트를 활용할 수 있습니다.
using UnityEngine;
using Daebak.Common.Collections;
public class Test_Stack : MonoBehaviour
{
void Start()
{
Stack<int> stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
stack.Push(30);
Debug.Log(stack.Peek()); // 30 출력
while (!stack.IsEmpty())
{
Debug.Log(stack.Pop()); // 30, 20, 10 순으로 출력
}
}
}
실행 결과
30
30
20
10
- Peek() : 데이터를 제거하지 않고 확인
- Pop() : 데이터를 꺼내면서 제거
- LIFO 구조의 특성이 그대로 반영된 결과
6.1 스택을 활용한 괄호 문자열 검증 예제
스택은 문자열의 괄호 구조가 올바른지 판단할 때도 효과적으로 사용할 수 있습니다. 여는 괄호 '('는 push, 닫는 괄호 ')'는 pop 하여 문자열 전체를 검증할 수 있습니다.
using UnityEngine;
using Daebak.Common.Collections;
public class Test_Stack : MonoBehaviour
{
void Start()
{
string txt;
txt = "(())()";
Debug.LogFormat("{0} - {1}", txt, Test(txt));
txt = "(()";
Debug.LogFormat("{0} - {1}", txt, Test(txt));
txt = "())";
Debug.LogFormat("{0} - {1}", txt, Test(txt));
}
private bool Test(string s)
{
Stack<char> stack = new();
foreach (char c in s)
{
if (c == '(')
{
stack.Push('(');
}
else if (c == ')')
{
if (stack.IsEmpty())
{
return false;
}
stack.Pop();
}
}
return stack.IsEmpty();
}
}
실행 결과
(())() - True
(() - False
()) - False
- (())() : 모든 괄호가 정확히 매칭되어 True
- (() : 여는 괄호가 남아 있으므로 False
- ()) : 닫는 괄호가 더 많아 False
마무리
스택은 구조가 단순하지만 다양한 알고리즘과 시스템 흐름에서 핵심적인 역할을 하는 자료구조입니다. 직접 구현해 보면 top 포인터의 의미, 배열 기반 저장 방식, 확장 처리 등을 명확하게 이해할 수 있습니다.
※ 다음 글에서는 Queue(큐) 구조를 살펴보며, FIFO 특성과 스택(Stack)과의 구조적 차이를 중심으로 정리합니다.
'자료 구조' 카테고리의 다른 글
| Unity 개발자를 위한 C# HashSet(해시셋) 구현 (0) | 2025.12.07 |
|---|---|
| Unity 개발자를 위한 C# Deque(덱) 구현 (0) | 2025.12.06 |
| Unity 개발자를 위한 C# LinkedList(링크드 리스트) 구현 (0) | 2025.12.05 |
| Unity 개발자를 위한 C# List(리스트) 구현 (0) | 2025.12.05 |
| Unity 개발자를 위한 C# Queue(큐) 구현 (0) | 2025.11.24 |