이 Step에서 다루는 것

  • “변수 1개로는 한계”가 생길 때, 데이터를 묶어서 다루는 가장 기본 구조 = 배열
  • 배열의 핵심 성질: 연속된 메모리 + 인덱스(0부터) + 크기 고정
  • 반복문과 결합했을 때 왜 강력해지는지
  • 배열에서 가장 흔한 치명적 실수: 범위 초과(Out-of-bounds) 와 그 결과(스택 오염)

학습 목표

  • 배열을 한 문장으로 정의할 수 있다.
  • “배열 크기 N이면 인덱스는 0~N-1”을 자동으로 떠올릴 수 있다.
  • i <= N이 왜 위험한지(무슨 일이 덮어써지는지) 설명할 수 있다.

자료구조와 알고리즘의 필요성

  • TextRPG 한계: 몬스터를 변수 1개로만 관리하면 “1마리”까지만 자연스럽습니다.
    100마리, 1000마리가 되면 monsterHp1, monsterHp2… 처럼 변수를 늘리는 건 사실상 불가능합니다.
  • 자료구조(Data Structure): 데이터를 어떻게 저장·관리할지에 대한 구조적 설계.
  • 알고리즘(Algorithm): 저장된 데이터를 어떻게 처리·가공할지에 대한 절차.
  • 배열은 자료구조 중 가장 기본이며, 다양한 알고리즘에서 사용됨.

배열이란?

항목설명
정의동일한 데이터 타입의 값을 연속된 메모리 공간에 저장하는 자료구조
인덱스0부터 시작. 최대 인덱스 = 배열 크기 - 1
크기선언 시 고정. 이후 변경 불가
메모리선언 시 크기만큼 연속적으로 책장처럼 이어붙여 할당됨
  • 연속성이 핵심. 일반 변수는 1칸, 배열은 N칸이 한 덩어리로 메모리에 올라감.

예: int arr[5]; 는 메모리에 이런 느낌으로 연속 배치됩니다.

arr[0]  arr[1]  arr[2]  arr[3]  arr[4]
┌─────┬─────┬─────┬─────┬─────┐
│  ?  │  ?  │  ?  │  ?  │  ?  │  (초기화 안 하면 쓰레기 값일 수 있음)
└─────┴─────┴─────┴─────┴─────┘

배열을 사용하는 이유

  • 관련된 데이터를 묶어서 편리하게 다루기 위해 사용.
  • 반복문과 결합하면 데이터를 일괄적으로 처리하기에 적합.
  • 게임 맵, 체스판, 이미지 픽셀, 행렬 등 구조적 데이터 저장에 유리.
  • 맵 예시: 2D 로그라이크에서 맵 정보는 거의 고정. 타일별로 벽/빈 공간/물 등을 표시할 때 배열 사용.

배열 문법과 접근

타입 배열이름[크기];
  • int arr[10]; → int 타입의 10칸짜리 배열. 배열 이름은 arr.
  • C++ 문법상 int 타입의 배열이다 → 타입이름, 크기를 함께 봐야 함.
  • 접근: arr[0], arr[1], ... arr[9]. 인덱스는 0부터 시작.

배열 초기화 방법

방법예시
선언만int arr[10]; → 쓰레기값
전체 초기화int arr[5] = {1, 2, 3, 4, 5};
일부만 초기화int arr[10] = {1, 2, 3}; → 나머지 7개는 0
반복문으로 초기화for (int i = 0; i < 5; i++) arr[i] = i * 10;
크기 생략int arr[] = {1, 2, 3, 4, 5}; → 컴파일러가 5로 추론

반복문을 통한 배열 초기화 및 출력

#include <iostream>

int main() {
    constexpr int N = 5;
    int arr[N];  // 크기 5의 배열 선언

    for (int i = 0; i < N; i++) {    // 0부터 4까지 반복
        arr[i] = i * 10;             // 각 요소에 값 대입 (0, 10, 20, 30, 40)
    }

    for (int i = 0; i < N; i++) {    // 배열 출력
        std::cout << arr[i] << '\n';
    }

    return 0;
}

출력 결과

0
10
20
30
40
  • 반복문 주의: i < 5 (크기 5일 때 0~4). i <= 5범위 초과 → 스택 오염.

스택 오염 (Stack Corruption)

배열에서 가장 위험한 실수는 “한 칸 더 쓰기”입니다.
이건 단순한 실수가 아니라 정의되지 않은 동작(UB) 으로, 증상이 매번 달라질 수 있습니다.

int arr[5];

for (int i = 0; i <= 5; i++) {  // ❌ 총 6회 반복 → arr[5]는 존재하지 않음
    arr[i] = i * 10;
}
  • arr의 유효한 인덱스 범위는 0 ~ 4.
  • i = 5일 때 arr[5] = 50;존재하지 않는 메모리 접근.
  • (MSVC 디버그 환경 등에서) 런타임 오류로 잡히는 경우:
    • Run-Time Check Failure #2 - Stack around the variable 'arr' was corrupted.
  • 크래시가 나면 오히려 다행입니다. 크래시 없이 “옆 메모리”만 덮어쓰면, 한참 뒤에 엉뚱한 곳에서 버그가 터질 수 있습니다.
  • 방지: 반복 조건은 반드시 i < 배열크기 형태로 지정.

스택에서 이런 일이 생긴다고 생각하면 됩니다(개념 그림).

┌─────────────── 스택 프레임 ───────────────┐
│ arr[0] arr[1] arr[2] arr[3] arr[4]        │  ← 배열의 유효 범위
│ [다른 지역 변수 / 저장된 값 / (디버그)보호값] │  ← arr[5]가 여기로 침범할 수 있음
└───────────────────────────────────────────┘

체크 질문 (스스로 답해보기)

  • i <= 5가 아니라 i < 5여야 할까?
  • “크래시가 안 나면 괜찮다”가 왜 위험한 생각일까?

1D vs 2D 배열 맵

  • 1D: int map[25]index = y * MAP_SIZE + x 공식으로 좌표→인덱스 변환.
    • y가 1 늘어나면 인덱스는 MAP_SIZE(5)만큼 점프. x는 열 내 위치.
  • 2D: int map[5][5]map[y][x]로 직접 접근. 구조적으로 더 직관적.
  • 메모리: 1D든 2D든 실제로는 연속된 메모리에 배치됨. 문법적 차이일 뿐.

예를 들어 MAP_SIZE = 5일 때, ((x, y))를 1D 인덱스로 바꾸는 공식은 다음입니다.

[
\text{index} = y \times \text{MAP_SIZE} + x
]

직관 체크(5×5):

y=0:  0  1  2  3  4
y=1:  5  6  7  8  9
y=2: 10 11 12 13 14
y=3: 15 16 17 18 19
y=4: 20 21 22 23 24
      x=0 1  2  3  4

즉, 2D 배열 map[y][x]도 내부적으로는 “행(row) 단위로 이어붙인 연속 메모리”라고 생각하면 됩니다. (행 우선, row-major)


profile
李家네_공부방

0개의 댓글