[자료구조] 정적 배열과 동적 배열 (Static Array & Dynamic Array)

[자료구조] 정적 배열과 동적 배열 (Static Array & Dynamic Array)

정적 배열과 동적 배열에 대해 정리한 글입니다. 기본 개념과 특징을 중점으로 정리하였습니다.


정적 배열 (Static Array)

정적 배열의 정의

  • 정적 배열은 배열의 크기가 컴파일 시 결정되는 배열의 의미
  • 일반적으로 스택 영역(stack section)에 할당되며, 프로그램이 시작될 때 필요한 메모리 공간을 이미 확보한 상태로 실행

메모리 구조

스택 영역 할당

  • 일반적으로 함수 내부에 정적 배열을 선언하면, 해당 함수가 호출될 때 스택에 고정된 크기의 메모리가 할당됨
  • 함수가 종료되면 스택이 정리되면서 배열도 소멸함

고정 크기

  • 배열의 크기를 변경할 수 없음
    • ex. int arr[10];으로 선언한 배열은 10개의 int 공간을 항상 차지함 (변경 불가)

정적 배열의 장단점

장점

  • 접근 속도가 빠름
    • 모든 요소가 연속적인 메모리에 저장되기 때문에 인덱스를 이용한 랜덤 액세스가 $ O(1) $ 시간에 가능
  • 단순한 선언과 해제
    • 별도의 동적 메모리 관리 함수(malloc, free 등)를 호출하지 않아도 됨
    • 선언과 동시에 메모리가 할당되고, 스코프가 끝날 때 자동으로 해제됨
  • 메모리 접근의 예측 가능성
    • 소멸 시점이 명확힉 때문에 메모리 누수(memory leak)에 대한 위험이 없음

단점

  • 크기 변경 불가
    • 배열 크기를 확장하거나 축소할 수 없으므로, 프로그램 실행 중에 필요한 크기가 달라지면 재설계가 필요
  • 스택 메모리 제한
    • 대규모 배열을 선언할 경우, 스택 오버플로우(stack overflow)가 발생할 수 있음
  • 재사용 유연성 낮음
    • 다른 함수에서 이 배열을 공용으로 사용하기 어렵거나, 사용할 경우 전역 변수로 두어야 해 결합도(coupling)가 높아질 수 있음

주의 사항

  • 함수 안에서 큰 크기의 배열을 선언할 때에는 스택 사용량 고려가 필요 (스택 오버플로우를 일이킬 수 있는지 확인)
  • 전역(파일 범위) 변수를 사용할 때 메모리 사용량을 면밀히 파악하고, 다른 모듈 및 함수와 결합도를 낮추도록 설계 필요

사용 예시

  • 크기가 확실하게 정해져 있는 경우
    • ex. 고정된 크기의 테이블을 생성하여 간단한 조회 용도로 사용하는 경우
  • 임베디드 시스템
    • 메모리 용량이 제한된 환경에서, 정적 할당으로 안전하게 메모리를 관리해야 할 때 활용

동적 배열 (Dynamic Array)

동적 배열의 정의

  • 동적 배열은 프로그램 실행 중에 메모리를 할당받아 크기를 자유롭게 결정하는 배열을 의미
  • C 혹은 C++에서는 주로 malloc, calloc, realloc, free (C 언어)나 new, delete (C++) 등을 통해 힙 영역(heat section)에 메모리를 할당하고 관리함

메모리 구조

힙 영역 할당

  • 동적 배열은 스택이 아닌 힙(heap)에서 메모리를 할당받음
  • 프로세스의 런타임 상황에 따라 필요한 크기만큼 메모리를 요구하고, 사용이 끝나면 반드시 직접 해제해줘야 함

크기 변경(재할당) 가능

  • realloc (C 언어 함수)을 사용하거나,, C++ STL의 std::vector 같은 자료구조를 사용하면 필요 시 배열 크기를 조절할 수 있음

동적 배열의 장단점

장점

  • 유연성
    • 실행 중에 사용자가 원하는 크기만큼 배열 공간을 할당할 수 있으므로, 상황에 따라 메모리를 효율적으로 사용할 수 있음
  • 재할당을 통한 확장
    • realloc 등을 사용해 배열 크기를 확장 가능
    • 단, 내부 구현상 새로운 메모리 공간을 할당하고 기존 데이터를 복사할 수도 있음
  • 스택 오버플로우 방지
    • 힙 영역을 사용하는 덕분에, 큰 용량의 배열이 필요한 경우에도 스택 영역에 부담을 주지 않음

단점

  • 메모리 관리의 복잡성
    • 사용 후에 반드시 해제(free / delete)해야 하며, 해제하지 않으면 메모리 누수가 발생
  • 할당 실패 가능성
    • 힙 공간이 부족하면 동적 메모리 할당이 실패할 수 있으며, 할당 실패에 대한 예외 처리가 필요
  • 재할당 시 비용 발생
    • 배열 크기를 변경하기 위해 재할당(realloc)이 발생하면, 새로 할당받은 메모리로 데이터를 복사하는데 $ O(n) $ 시간이 소요될 수 있음
  • 추가 오버헤드
    • 힙 메모리는 스택에 비해 할당 및 해제 자체가 무거운 연산이며, 메모리 단편화(fragmentation)가 발생할 수 있음

주의 사항

  • 할당받은 메모리를 사용한 뒤에는 반드시 해제해야 하며, 중복 해제(double free)나 잘못된 포인터(invalid pointer) 참조에 주의가 필요
  • 예외(Exception)나 에러 발생 시에도 제대로 해제할 수 있도록 RAII(Resource Acquisition Is Initialization) 패턴이나 smart pointer (C++에서 unique_ptr, shared_ptr))등을 활용하는 것이 좋음
  • realloc 혹은 내부 구현상 배열이 재할당될 경우 (ex. C++에서 std::vector가 용량을 확장하는 경우) 이전 포인터가 무효화될 수 있어 포인터 사용 시 주의가 필요

사용 예시

  • 크기를 알 수 없거나 변동이 많은 경우
    • ex. 사용자 입력에 따라 데이터 개수가 달라질 수 있는 프로그램
  • C++ STL의 std::vector
    • 내부적으로 동적 배열을 이용하며, 필요 시 자동으로 학장
    • 사용자느 ㄴ인덱스를 이용해 접근하거나, 동적 크기를 다룰 수 있어 편리함
  • 임시 버퍼 및 대규모 데이터 처리
    • 파일 입출력, 네트워크 패킷 처리 등에서 런타임에 필요한 크기만큼 배열을 할당하고 작업이 끝나면 해제함

정적 배열과 동적 배열 비교

항목 정적 배열 (Static Array) 동적 배열 (Dynamic Array)
메모리 할당 시점 컴파일 시
(또는 함수 호출 시 스택에 할당)
런타임 시
(힙 영역에 할당)
크기 변경 가능 여부 불가능 가능 (재할당을 통한 확장 / 축소)
할당 / 해제 관리 자동
(스코프 종료 시 해제)
수동
(free, delete 등 사용)
장점 간단한 선언 및 접근
빠른 접근 속도
예측 가능한 메모리 사용
런타임에 유연한 크기 결정
확장 / 축소 가능
대규모 데이터 처리 용이
단점 크기 변경 불가
스택 메모리 제한
재사용 어려움
메모리 누수 위험
재할당 시 복사 비용
힙 단편화 가능성
대표적 사용 예시 컴파일 시 크기가 고정된 데이터
임베디드 환경
사용자 입력, 파일, 네트워크처럼
크기 변화가 많은 데이터
std::vector, 동적 버퍼

핵심 요약

  1. 정적 배열
    • 크기가 고정적이고, 컴파일 시점에 확정되는 경우 유리함
    • 선언과 해제 과정이 간단하며, 스코프가 명확함
  2. 동적 배열
    • 크기가 유동적이고 변동 사항이 많을 때 유용함
    • 메모리 관리를 직접 해야 하므로, 메모리 누수와 같은 오류에 주의가 필요