본문 바로가기
KNOU CS

[C] 2차원 배열의 포인터 연산으로 메모리 주소값 찾기

by 보눔비스타 2025. 9. 14.

이번 포스팅에서는 C언어에서 2차원 배열과 메모리 주소와의 관계에 대해 알아보고, &, *, + 같은 포인터 연산자가 어떻게 동작하는지 예제를 통해 자세히 뜯어보려고 한다.

포인터를 잘 이해하면 임베디드의 핵심인 메모리를 잘 다루는 데 도움이 된다고 하니 잘 익혀두는 것이 좋겠다. 

 

우선 다음의 2행 3열의 배열의 각 요소들이 메모리에 어떤 순서로 저장되는지 살펴보자. 

int main(void)
{

	int a[2][3] = {{0, 1, 2}, {3, 4, 5}};

}

 

먼저, int a[2][3] = {{0, 1, 2}, {3, 4, 5}}; 배열의 메모리 구조를 이해하는 것이 중요하다.

C 언어에서 2차원 배열은 행(row) 우선으로 메모리에 연속적으로 저장된다.

int의 크기가 4바이트이므로, 각 요소의 주소는 4바이트씩 증가한다.

 

그러면 위 2차원 배열의 각각의 주소값은 다음과 같이 된다.

  • a[0][0] (값 0): 0x1000
  • a[0][1] (값 1): 0x1004
  • a[0][2] (값 2): 0x1008
  • a[1][0] (값 3): 0x100C
  • a[1][1] (값 4): 0x1010
  • a[1][2] (값 5): 0x1014

 

배열의 포인터 연산

 

그러면 이제 위 배열을 가지고 다음 6가지의 포인터 연산을 수행해보자.

 

1) &a = ?

&a는 전체 배열 a의 시작 주소를 의미한다.

a는 int (*)[2][3] 타입의 포인터로 배열 전체를 가리키며, 그 값은 배열의 첫 번째 요소인 a[0][0]의 주소와 동일한 0x1000이다.

 

2) &a+1 = ?

&a + 1는 전체 배열(&a)의 시작 주소에 1을 더하는 연산이다.

포인터 연산에서 1을 더한다는 것은 가리키는 대상의 크기만큼 주소를 이동시키는 것을 의미한다.

&a가 가리키는 대상은 전체 배열이므로, 배열 a의 전체 크기(2행 × 3열 × 4바이트 = 24바이트)만큼 주소가 이동하게 된다.

0x1000 + 24 = 0x1018

 

3) a+1 = ?

a + 1: 배열의 이름 a는 그 자체로 첫 번째 행을 가리키는 포인터로 취급된다(int (*)[3]). 

이 포인터에 1을 더하면 한 행의 크기만큼 주소가 이동한다.

한 행은 3개의 int 요소로 이루어져 있으므로, 3 × 4바이트 = 12바이트만큼 이동하게 된다.

0x1000 + 12 = 0x100C

 

4) *a = ?

*a: *는 포인터가 가리키는 대상을 가져오는 연산이다.

배열 이름 a는 첫 번째 행인 a[0]를 가리키는 포인터이다.

따라서 *a는 a가 가리키는 대상, 즉 a[0]를 의미한다.

a[0]는 1차원 배열(int [3])이지만, *a의 결과로 사용될 때, a[0]는 다시 자신의 첫 번째 요소(a[0][0])를 가리키는 포인터(int*)로 변환된다.

따라서 a[0][0]의 주소는 배열의 시작 주소와 같은 0x1000이므로, a[0]가 변환된 포인터의 값도 0x1000이 된다.

 

5) *a+1 = ?

*a + 1: *a는 a[0][0]의 주소인 0x1000이다. 이 주소는 int 타입의 포인터(int*)이므로, 1을 더하면 int 하나의 크기인 4바이트만큼 주소가 이동한다.

0x1000 + 4 = 0x1004

 

6) **a = ?

2차원 배열은 '배열의 배열'이라는 구조를 가지므로, 이중 포인터 연산의 개념이 적용된다.

**a는 이중 역참조 연산으로 *(*a)와 같다.

 

  • a는 배열의 첫 번째 행 a[0]을 가리키는 포인터.
  • *a는 첫 번째 행의 첫 번째 요소 a[0][0]의 주소(0x1000)를 가리킨다.
  • 여기에 다시 *를 적용하면 해당 주소에 저장된 을 가져온다.
  • 0x1000 주소에는 a[0][0]의 값인 0이 저장되어 있다.
  • 따라서 최종 값은 0이다.

 

이처럼 C 언어의 포인터 연산은 가리키는 대상의 타입에 따라 이동하는 단위가 달라진다는 점을 기억하자.

배열의 이름이 단순히 주소만 가진 것이 아니라, 타입 정보까지 포함된 포인터라는 점을 이해하면 그리 복잡한 연산은 아닐 것이다.