왜 Array Index는 0부터 시작하는가: 그 숨은 진짜 이유

발행: (2026년 1월 17일 오후 03:03 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

If you’ve ever written code, you’ve probably asked this question at least once:

“왜 배열은 1이 아니라 0부터 인덱스를 시작할까요?”

처음 보면 1부터 시작하는 것이 더 자연스럽게 느껴집니다—사람은 1, 2, 3…이라고 셉니다—하지만 거의 모든 주요 프로그래밍 언어(C, C++, Java, Python, JavaScript, Go, Rust 등)는 0 기반 인덱싱을 사용합니다. 이것은 무작위 결정이 아닙니다. 아래에서는 하드웨어 메모리부터 컴파일러 설계까지 실제 이유를 살펴보고, 인덱스 0이 실제로 가장 효율적인 선택인 이유를 알아봅니다.

메모리 레이아웃 및 오프셋

배열은 연속된 메모리 블록이다.

arr = [10, 20, 30, 40]

Memory address:
1000 → 10
1004 → 20
1008 → 30
1012 → 40
  • 베이스 주소 – 첫 번째 요소의 주소(여기서는 1000).
  • 요소 크기 – 요소당 바이트 수(여기서는 4 바이트).

요소의 주소는 다음과 같이 계산한다:

address = base_address + (index * element_size);
  • arr[0]1000 + (0 * 4) = 1000
  • arr[1]1000 + (1 * 4) = 1004
  • arr[2]1000 + (2 * 4) = 1008

인덱스 0은 오프셋이 없음을 의미한다—요소가 정확히 베이스 주소에 위치한다.
배열이 1부터 시작한다면, 식은 다음과 같이 바뀐다:

address = base_address + ((index - 1) * element_size);

추가된 “‑1”은 모든 접근에 대해 뺄셈을 한 번씩 더하게 만들어 실행 속도를 늦추고 컴파일러 로직을 복잡하게 만든다.

C에서의 포인터 연산

제로 기반 인덱싱은 포인터 연산과 자연스럽게 맞물리기 때문에 C가 이를 대중화했습니다.

arr[i]  ==  *(arr + i);
  • arr은 첫 번째 요소를 가리키는 포인터입니다.
  • i는 오프셋(앞으로 몇 개의 요소를 이동할지)입니다.

따라서:

arr[0] = *(arr + 0);   // 첫 번째 요소
arr[1] = *(arr + 1);   // 두 번째 요소

C의 의미 체계를 물려받은 언어들(Java, JavaScript, Python, Go, Rust, …)은 일관성과 성능을 위해 제로 기반 인덱싱을 유지했습니다.

깔끔한 루프 구조

0부터 시작하는 인덱싱은 루프를 간결하게 만들고 기계 수준에서 발생하는 오프‑바이‑원 오류를 방지합니다.

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}
  • 0부터 시작합니다.
  • length 에 멈춥니다.

length는 마지막 인덱스가 아니라 요소의 개수이므로, 루프 조건 i < arr.length는 자연스럽게 올바릅니다.

인덱싱이 1부터 시작한다면, 루프 로직에 추가적인 뺄셈이나 조정된 종료 조건이 필요해 불필요한 비교가 늘어납니다.

반열 구간과 슬라이싱

많은 언어들이 슬라이스에 대해 반열 구간 [start, end) 규칙을 채택합니다:

arr = [10, 20, 30, 40, 50]
sub = arr[0:3]   # → [10, 20, 30]
  • 시작 인덱스 = 기준으로부터의 오프셋.
  • 끝 인덱스 = 포함할 요소의 개수.

이로 인해:

  • 연속된 슬라이스 간에 겹침이 없습니다.
  • 슬라이스 길이에 대한 모호성이 없습니다 (end - start).

반열 구간은 수학적으로 깔끔하며 슬라이싱, 범위 루프, 그리고 반복자 연산을 효율적으로 구현할 수 있게 합니다.

수학 및 이론적 기초

  • 시퀀스는 0부터 인덱싱됩니다 (첫 번째 요소의 오프셋은 0입니다).
  • 그래프 탐색, 유한‑상태 기계, 그리고 자동 이론은 시작 상태로부터의 거리를 나타내기 위해 0‑기반 번호 매김을 사용합니다.

인덱스를 인간‑지향적인 서수가 아닌 거리로 취급하면 이러한 모델과 일치하고 증명 및 알고리즘 설계를 단순화합니다.

대규모에서의 성능 영향

하나의 추가 뺄셈(index - 1)은 사소해 보일 수 있지만, 다음을 고려하십시오:

  • 수백만 개의 배열 접근이 빡빡한 루프 안에서 이루어짐(예: 그래픽 엔진, 과학 시뮬레이션).
  • 캐시 친화성: 0 기반 계산은 주소 연산에 직접 매핑되어 명령어 수를 줄입니다.
  • 컴파일러 최적화: 더 단순한 주소 계산은 보다 효율적인 생성 코드를 가능하게 합니다.

대규모에서는, 접근당 아주 작은 오버헤드라도 제거하면 측정 가능한 성능 향상으로 이어질 수 있습니다.

비‑0 기반 인덱싱을 사용하는 언어

일부 언어는 인간에게 친숙한 인덱싱 방식을 선택합니다:

  • MATLAB – 1‑기반 배열.
  • Lua – 기본적으로 1‑기반(하지만 변경 가능).
  • Fortran – 역사적으로 1‑기반.

이러한 언어에서도 컴파일러는 결국 배열 접근을 내부적으로 0‑기반 메모리 오프셋으로 변환하므로, 변환 비용이 발생합니다.

요점

  • 메모리는 기본 주소로부터 오프셋을 사용합니다.
  • 0 기반 인덱싱은 포인터 연산과 완벽하게 일치합니다.
  • 이는 더 간단하고 빠른 루프반개방 구간 의미론을 제공합니다.
  • 이는 시퀀스 시작점으로부터의 거리에 대한 수학적 개념과 일치합니다.

하드웨어 수준의 이유를 이해하면 인덱스 0이 임의적인 것이 아니라 불가피하게 느껴집니다.

Back to Blog

관련 글

더 보기 »

🚂 Arrays를 5살 아이에게 설명하기

기차를 상상해 보세요. 번호가 매겨진 객차가 있는 기차: 🚂 Car 0 Car 1 Car 2 Car 3 Car 4 각 객차는 0부터 시작하는 번호를 가지고 있으며, 하나의 항목을 담을 수 있습니다. Arrays ar...