배열, 더 엄밀한 배열

int a[10];

위와 같은 변수를 보았을때, 우리는 왜 이 변수를 배열이라고 생각하는 것일까?

배열(array)은 같은 타입의 변수들로 이루어진 유한 집합으로 정의됩니다. 배열을 구성하는 각각의 값을 배열 요소(element)라고 하며, 배열에서의 위치를 가리키는 숫자는 인덱스(index)라고 합니다. ...
- tcpschool.com

보통 위와 같이 int형 16개로 이루어진 배열이 바로 a이라고 설명한다. 우리는 이 문장을 보고, 평소에 해왔던 고수준 언어들의 특징들을 조합하여, aint형 10개로 이루어진 배열일 것이라고 추론한다.

하지만, K&R C에서는 다음과 같이 설명한다:

defines an array a of size 10, that is, a block of 10 consecutive objects named a[0], a[1], ... , a[9].
- K&R C, second edition (1988)

즉, a가 10개의 원소를 담고있는 배열인것이 아니라, a라는 이름의 변수가 메모리상에 연속하여 10개 존재하고, 이들을 구분짓기위해 index라고들 부르는것을 이용하여 a[i]라고 작성하기로 약속한 것이다.

따라서, a[0], a[1], ... , a[9]각각은 메모리상에서 연속될 뿐, 근본적으로 모두 독립된 변수다.

배열과 포인터

혹자는 배열이 포인터라고 말한다. 그렇게 생각하면 편하긴 한데, 엄밀히 따지면 다른점이 있다:

sizeof

int a[10];

배열 a는 포인터와 같이 연산할 수 있고, 역참조 될수도 있는데, 무엇이 다르다는 걸까?

이를 이해하기 위해 배열의 크기를 재보자.

sizeof a는 누구나 40이라고 추론할 수 있고, 실제로 그렇다.

하지만, a가 정녕 포인터였다면, 4(i386) 혹은 8(x86-64)이어야 맞지 않나?

즉, a[10]은 a가 10개 존재한다는 정보를 보존하고 있다는 말이다.

배열의 붕괴(decay)

이러한 정보는 손실될 수 있고, 이를 배열의 붕괴(decay)라고 부른다.

int v = *(a + 1);

배열에도 포인터와 같이 정수와의 연산이 가능하다. a + 0는 0번째 a, a + 1은 1번째 a와 같은 식이다.

여기서 의문이 등장한다. a + 1 1번째 a의 주소를 반환할까?

바로, a가 int*처럼 취급되었기 때문이다. 이는 배열이 배열로써의 정보를 잃어버리고, int*의 성질을 띄게되었음을 의미하며, 이를 배열의 붕괴(decay)라고 부르는 것이다.

&a + 1

반대로, 위의 코드는 40바이트만큼 오프셋이 증가한다. a[10]의 주소를 가져옴으로써, 배열의 정보가 보존되었기 때문이다.