여러 프로그래밍 언어들을 공부하면서, Array나 List의 인덱스가 0부터 시작하는 것은 "상식"이 되었다. 그렇지만 "왜?"라는 내용에 대한 답은 명확하게 내릴 수 없었다. 그래서 이번 기회에 알아봤다.
다음과 같은 배열이 있다고 하자.
int arr[100]
배열의 인덱스가 0부터 시작하는 이유는 컴파일러가 arr[i]를 해석하는 방식과 관련이 있다.
arr[i]는 *(arr+i)와도 같다. 즉, 이 arr은 배열의 주소 또는 0번쨰 인덱스의 배열 요소를 의미한다. 배열의 요소들은 메모리에서 연속적으로 저장되므로, 다음 요소의 주소는 arr + 1, 그 다음 요소의 주소는 arr + 2가 된다. 일반적으로 arr + i는 배열의 시작 요소에서 i만큼 떨어진 위치의 주소를 의미한다.
따라서, 배열의 시작 요소(0번째 요소)는 배열의 시작점에서 0만큼 떨어진 위치에 있으므로, i 값은 0이 되어야 한다. 이러한 논리를 따르다 보면, 배열의 인덱싱은 자연스럽게 0부터 시작하게 된다.
#include <iostream>
using namespace std;
int main()
{
int arr[] = { 1, 2, 3, 4 };
// 아래 두 구문은 완전히 같은 동작을 한다.
cout << *(arr + 1) << " ";
cout << arr[1] << " ";
return 0;
}
현대 프로그래밍 언어에서는 행 우선(row-major) 순서를 사용하여 2차원 배열을 저장한다. 아래는 2차원 배열의 인덱스가 시작되는 위치에 따른 주소 계산이다.
&( arr[i][j] ) = address + [ ( i-1 )*n + ( j-1 ) ]*( sizeof(int) ) ]
여기서 (i-1)과 (j-1)이 추가적으로 계산되므로, 총 6개의 연산이 필요하다.
&( arr[i][j] ) = address + [ ( i )*n + ( j ) ]*( sizeof(int) ) ]
이 경우 (i-1)이나 (j-1) 같은 추가적인 연산이 없으므로, 총 4개의 연산만 필요하다.
위의 계산을 보면 0부터 시작하는 배열의 경우 연산 횟수가 2개 줄어든다. 이 차이는 작은 데이터에서는 미미할 수 있지만, 대규모 데이터를 처리할 때는 성능과 속도에 영향을 미칠 수 있다.
따라서, C++, Python, Java 같은 대부분의 언어들은 0부터 시작하는 인덱스를 사용하며, Lua 같은 일부 언어만 1부터 시작하는 인덱스를 사용한다.