본문 바로가기

C 언어

c언어_2차원 배열과 포인터의 관계(1)

 

2차원 배열에서는 포인터의 역할이 더 복잡해진다.

2차원 배열은 행과 열로 구성되는데, 1차원 배열에서 배열의 이름은 배열의 첫 번째 요소의 주소를 가리켰다면, 2차원의 경우 배열 이름은 첫 번째 행의 첫 번째 요소를 가리킨다.

 

자세한 설명을 위해, 2차원 배열 a, b 가  있다고 가정하자. 

int a[3][2];
int b[4][3];

 

배열의 이름은 1차원이든 2차원이든 무조건 그 배열의 시작 주소이다.

즉, a,b 라는 배열의 이름은 그 배열의 시작 주소를 나타내는 포인터이다. 

 

1차원 배열에서, 배열의 이름을 1씩 증가할 때,

int형이면 4바이트씩 증가하고,

double이면 8바이트씩 증가하고,

char면 1바이트씩 증가한다.

 

자, 그러면 2차원 배열의 포인터도 int 타입이니까 무조건 4바이트씩 증가할까?

여기서 1차원 배열과 2차원 배열의 차이점이 나타난다. 

  • 1차원 배열에서 a + 1은 다음 요소의 주소로 이동한다. 
  • 2차원 배열에서 a + 1은 다음 행의 주소로 이동한다.

이해가 안간다면, 좀 더 자세히 설명해보겠다. 

1차원 배열에서는 배열의 이름 a를 포인터처럼 사용하며, 이를 1씩 증가시키면 각 요소의 크기만큼 메모리 주소가 증가한다. 예를 들어, int형 배열인 경우, a + 1은 첫 번째 요소에서 4바이트(즉, int의 크기)만큼 이동한 두 번째 요소의 주소를 가리키게 된다. 

 

하지만 2차원 배열에서는 행 단위로 포인터가 이동한다. 포인터를 1씩 증가시키면 열의 개수만큼 메모리 주소를 점프하고, 다음 행의 첫 번째 요소를 가리킨다. 즉, 2차원 배열의 열의 따라서 포인터의 크기가 달라지는 것이다.

 

예를 들어, a[3][2] 배열을 보자.

a 배열이 2열짜리이기 때문에 1씩 증가할 때마다 8바이트씩 증가하게 된다.

왜냐하면 4바이트짜리 int형 데이터가 2개씩 있기 때문에, 1행을 건너뛸 때 8바이트를 건너뛰게 되기 때문이다. 

 

그러면 b[3][4] 배열도 살펴보자. 

b 배열은 4바이트짜리 int형 데이터가 3개씩 있으니까 1씩 증가할 때마다 12바이트씩 증가하게 된다.

왜냐하면 4바이트짜리 3개를 한 번에 점프해야 하기 때문이다. 

그렇다면 배열에 대한 포인터 타입을 어떻게 선언해야 할까?

예를 들어, int형 포인터를 이렇게 선언한다고 생각해 보자.

int *p;
 

이 포인터는 int형 변수를 가리키는 포인터이다. 즉, 이 포인터는 4바이트씩 점프할 수 있는 포인터가 되는 것이다.

1차원 배열에서는 이렇게 선언해도 문제가 없었다. 그런데 현재 선언된 포인터 p는 8바이트씩 증가하는 a 배열과, 12바이트씩 증가하는 b 배열을 가리킬 수가 없다.

 

따라서 2차원 배열의 포인터를 선언할 때는 int형의 데이터를 몇 개씩 점프할건지를 넘겨주어야 한다. (열의 개수)

그래서 2차원 배열에서는 다음과 같이 괄호를 이용해 포인터 타입을 선언해주면 된다. 

int (*p1)[2]; // p1은 int형의 변수를 2개씩 점프하는 포인터 
int (*p2)[3]; // p2는 int형의 변수를 3개씩 점프하는 포인터

 

이렇게 선언하면, 이 포인터는  int형의 데이터를 인덱스 안의 숫자만큼 건너뛰는 포인터가 된다. 즉, 

p1+1은 a[0]에서 8바이트를 건너뛰어 a[1]을 가리키게 되고,

p2+1은 b[0]에서 12바이트를 건너뛰어 b[1]을 가리키게 된다. 

 

그리고, 2차원 배열의 포인터 타입을 선언할 때는 반드시 괄호를 써줘야 하는데, 이건 약속이다. 

괄호를 쓰지 않으면 포인터 배열로 인식되어 다른 의미가 될 수 있기 때문이다. 

  • 괄호 썼을 때 : p1은 int형의 변수를 2개씩 점프하는 포인터 
int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}}; 
int (*p1)[2] = arr; // p1은 arr[0]을 가리키는 포인터

//p1 + 1은 arr[1]의 주소를 가리키고, 역참조 (*p1)[0]은 arr[0][0]의 값을 가리킴
  • 괄호 안썼을 때 : p1은 정수 2개의 주소값을 저장할 수 있는 int형 포인터 배열
int a = 10, b = 20; 
int *p1[2] = {&a, &b}; // p1[0]은 a를, p1[1]은 b를 가리킴