포인터의 포인터(이중 포인터)에 대한 이해

프로그래밍/C 2019.04.18 댓글 Plorence

보통 그냥 포인터라고 하면 포인터를 선언할 당시에 *연산자가 하나만 쓰입니다.
선언할 때 *연산자를 더 늘려서 이중, 삼중까지 가능합니다.
영어로는 더블 포인터,트리플 포인터라고 부릅니다.

이중 포인터

이중 포인터는 그나마 쓰이지만 삼중 포인터는 잘 안 쓰입니다.
이중 포인터는 2차원 배열이나 포인터 그자체를 다뤄야 할 때 많이 쓰입니다.

 

이중 포인터 선언

Type ** Name;

*연산자 1개 더 붙여주면 됩니다.

 

이중 포인터에 대한 이해

이중 포인터를 이해하려면 단일 포인터와 *연산자를 빠삭하게 이해하고 있어야 합니다.

#include <stdio.h>
int main(void) {
       int num = 10;
       int * ptr = &num;
       int ** dptr = &ptr;
}

이중 포인터 그림

하나씩 어떠한 값을 가지고 있는지 알아봅시다.
이때
ptr의 주소값 004FFE7C
num의 주소값을 008FF9A4 라고 합시다.

의미(가지고 있는 값)
dptr 포인터 변수 ptr의 주소값 (004FFE7C)
*dptr 포인터 변수 ptr가 가지고 있는 값 (008FF9A4)
**dptr 포인터 변수 ptr가 지시하고 있는 값 (10)

아래는 예제입니다.

#include <stdio.h>
int main(void) {
       int num = 10;
       int * ptr = &num;
       int ** dptr = &ptr;
       printf("num의 주소값:%p num의 값:%d \n", &num, num);
       printf("ptr의 주소값:%p , ptr가 지시하고 있는 값:%d , ptr가 가지고 있는 값:%p  \n", &ptr, *ptr,ptr);
       printf("dptr의 주소값:%p , dptr가 지시하고 있는 값:%p , dptr가 가지고 있는  값:%p \n", &dptr, *dptr, dptr);
       printf("dptr가 가지고 있는 값:%p *dptr가 가지고 있는 값:%p , **ptr가 가지고  있는 값:%d", dptr, *dptr, **dptr);
}

해당 출력 결과는 아래와 같습니다.

num의 주소값:010FFA38 num의 값:10
ptr의 주소값:010FFA2C , ptr가 지시하고 있는 값:10 , ptr가 가지고 있는 값:010FFA38
dptr의 주소값:010FFA20 , dptr가 지시하고 있는 값:010FFA38 , dptr가 가지고 있는 값:010FFA2C
dptr가 가지고 있는 값:010FFA2C *dptr가 가지고 있는 값:010FFA38 , **ptr가 가지고 있는 값:10

그래서 어디에 쓰이는가?

아래는 2차원 배열에 대한 예제입니다.

#include <stdio.h>
void Print2D(int ** ptr) {
       for (int i = 0; i < 3; i++) {
              int * ptr_temp = (ptr+i*3); //가로길이가 3이라서 다음 [i+1][0]배열에  접근할려면 *3해줘야됨
              for (int j = 0; j < 3; j++) {
                     printf("%d \n",*ptr_temp+j);
              }
       }
}
int main(void) {
       int arr[3][3] = {
              {1,2,3},
              {4,5,6},
              {7,8,9}
       };
       Print2D(arr);
       
}

근데 이렇게 사용하는건 매우 좋지 않다고 생각합니다.

[] 연산자를 사용하여 접근하는 게 보기도 좋고 이해하기도 훨씬 쉽습니다.

파라미터 부분에서도 **보다는 [3][]라고 써주며 "2차원 배열을 인자로 받을 거야"라고 알려주는 게 좋습니다.

 

아래 예제는 포인터에 대한 예제입니다.

#include <stdio.h>
#include <stdlib.h>
void MemoryAlloc(int ** ptr) {
       *ptr = malloc(sizeof(int));
       **ptr = 100;
}
int main(void) {
       int num = 10;
       int * ptr = &num;
       printf("ptr 가지고 있는 값:%p ptr 지시하고 있는 값:%d \n", ptr, *ptr);
       MemoryAlloc(&ptr);
       printf("ptr 가지고 있는 값:%p ptr 지시하고 있는 값:%d \n", ptr, *ptr);
       free(ptr);
       
}

사용자 정의 함수에서 포인터 자체를 다뤄야 할 경우에 쓰입니다.

이걸 보고 계신분이라면 동적 할당을 잘 모르시는 분이 대다수 일 겁니다.

위의 예제는 런타임 도중에 변수 하나를 더 만들어서 ptr가 지시하는 변수를 바꿔버립니다.

ptr 가지고 있는 값:00EFFDDC ptr 지시하고 있는 값:10
ptr 가지고 있는 값:000A9DE0 ptr 지시하고 있는 값:100

출력 결과

 

2차원 배열의 이름 특성과 주의사항

배열 포인터와 포인터 배열을 혼동하면 안 됩니다.

int * WhoA [4]; //포인터 배열
int (*whoB) [4]; //배열 포인터 

포인터 배열은 포인터 변수로 이루어진 배열이고

배열 포인터는 배열을 가리킬 수 있는 포인터 변수입니다.

 

2차원 배열을 함수의 인자로 전달하려면 두 가지 방법이 있습니다.

void SimpleFunc(int (*parr1)[7]){...}
void SImpleFunc(int parr1[]){...}

 

댓글