정수형 변수 하나의 크기가 4바이트이고, 포인터 변수 하나의 크기가 8바이트라고 가정해 봅시다. 그렇다면 아래 코드를 실행했을 때 출력 결과는 과연 무엇일까요?
예제 코드
#include<iostream>
using namespace std;
main() {
int a[4][5][6];
int x = 0;
int* a1 = &x;
int** a2 = &a1;
int*** a3 = &a2;
cout << sizeof(a) << " " << sizeof(a1) << " " << sizeof(a2) << " " << sizeof(a3) << endl;
cout << (char*)(&a1 + 1) - (char*)&a1 << " ";
cout << (char*)(&a2 + 1) - (char*)&a2 << " ";
cout << (char*)(&a3 + 1) - (char*)&a3 << " ";
cout << (char*)(&a + 1) - (char*)&a << endl;
cout << (char*)(a1 + 1) - (char*)a1 << " ";
cout << (char*)(a2 + 1) - (char*)a2 << " ";
cout << (char*)(a3 + 1) - (char*)a3 << " ";
cout << (char*)(a + 1) - (char*)a << endl;
cout << (char*)(&a[0][0][0] + 1) - (char*)&a[0][0][0] << " ";
cout << (char*)(&a[0][0] + 1) - (char*)&a[0][0] << " ";
cout << (char*)(&a[0] + 1) - (char*)&a[0] << " ";
cout << (char*)(&a + 1) - (char*)&a << endl;
cout << (a[0][0][0] + 1) - a[0][0][0] << " ";
cout << (char*)(a[0][0] + 1) - (char*)a[0][0] << " ";
cout << (char*)(a[0] + 1) - (char*)a[0] << " ";
cout << (char*)(a + 1) - (char*)a;
}이 문제를 풀기 위해서는 다음과 같은 핵심 개념들을 먼저 이해해야 합니다.
정수는 4바이트(32비트), 포인터는 8바이트입니다. 포인터에 1을 더하면 그 포인터가 가리키는 타입의 크기만큼 주소가 앞으로 이동합니다.
&a1은 int** 타입, &a2는 int*** 타입, &a3는 int**** 타입입니다. 셋 모두 포인터를 가리키고 있으므로, 여기에 1을 더하면 주소가 8바이트씩 증가합니다.
a[0][0][0]은 정수이고, &a[0][0][0]은 int* 타입입니다. a[0][0] 역시 int* 타입이며, &a[0][0]은 int(*)[6] 타입입니다. 같은 원리로 &a[0]은 int(*)[5][6], &a는 int(*)[4][5][6] 타입이 됩니다.
출력 결과
480 8 8 8 8 8 8 480 4 8 8 120 4 24 120 480 1 4 24 120
결과 상세 해설
첫 번째 줄: sizeof 연산 결과
배열 a의 전체 크기는 4 × 5 × 6 × 4바이트 = 480바이트입니다. 반면 a1, a2, a3는 각각 포인터 변수이므로 크기가 모두 8바이트로 출력됩니다.
두 번째 줄: 주소(&)에 1을 더한 경우
&a1, &a2, &a3는 모두 '포인터를 가리키는 포인터'이므로 1을 더하면 8바이트씩 이동합니다. 반면 &a는 배열 전체를 가리키는 포인터(int(*)[4][5][6])이므로 1을 더하면 배열 전체 크기인 480바이트만큼 이동합니다.
세 번째 줄: 포인터 자체에 1을 더한 경우
a1은 int*이므로 1을 더하면 4바이트, a2와 a3는 포인터를 가리키므로 각각 8바이트씩 이동합니다. 배열 이름 a는 첫 번째 블록(int[5][6])을 가리키도록 decay되므로, a + 1은 5 × 6 × 4 = 120바이트만큼 이동합니다.
네 번째 줄: 배열 요소의 주소에 1을 더한 경우
&a[0][0][0]은 정수 하나를 가리키므로 4바이트, &a[0][0]은 길이 6짜리 행을 가리키므로 6 × 4 = 24바이트, &a[0]은 5×6 크기의 블록을 가리키므로 5 × 6 × 4 = 120바이트, &a는 배열 전체를 가리키므로 480바이트만큼 이동합니다.
다섯 번째 줄: 값 자체와 포인터 산술의 차이
a[0][0][0] + 1은 단순한 정수 연산이므로 차이가 1로 출력됩니다. 반면 a[0][0]은 int*이므로 4바이트, a[0]은 int(*)[6]이므로 24바이트, a는 int(*)[5][6]이므로 120바이트만큼 이동합니다.
이처럼 포인터 연산에서 얼마나 많은 바이트가 이동하는지는 포인터가 무엇을 가리키느냐에 따라 결정됩니다. 특히 다차원 배열에서 배열 이름이 어떤 타입으로 decay되는지 정확히 이해하는 것이 이 문제의 핵심입니다.