이중포인터 매개변수 - ijungpointeo maegaebyeonsu

포인터의 포인터는 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻함

"이중 포인터" or "더블 포인터" 라고 부름

** : 이중 포인터 변수 or 더블 포인터 변수

* : 싱글 포인터 변수

포인터 변수를 가리키는 이중 포인터 변수(더블 포인터 변수)

포인터 변수는 종류에 상관없이 무조건 주소 값을 저장하는 변수.

다만 차이가 나는 것은 포인터 변수가 가리키는 대상일 뿐

int main(void)
{
	double num = 3.14;
	double* ptr = # // 변수 num의 주소 값 저장
}

변수 num과 포인터 변수 ptr의 차이점과 공통점

공통점 : 둘 다 변수임. 따라서 값의 저장이 가능하다.

차이점 : 저장하는 값의 종류(유형)이 다르다.

  • ptr도 메모리 공간에 할당이 되는 변수.
  • 따라서 이를 대상으로도 & 연산이 가능하며, 이 때 반환되는 주소 값은 'double형 더블 포인터 변수'에 저장이 가능하다.
  • 떄문에 싱글 포인터 변수 ptr을 대상으로 다음과 같은 문장이 구성 가능
double** dptr = &ptr;

dptr과 ptr 및 num에 대해 다음과 같이 접근이 가능

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

*dptr = .....;  → *dptr은 포인터 변수 ptr을 의미함

*(*dptr) = ....; → *(*dptr)은 변수 num을 의미함(괄호 생략 가능)

예제

#include <stdio.h>

int main(void)
{
	double num = 3.14;
	double* ptr = &num;
	double** dptr = &ptr;
	double* ptr2;

	printf("%9p %9p \n", ptr, *dptr);
	printf("%9g %9g \n", num, **dptr);

	ptr2 = *dptr; // ptr2 = ptr 과 같은 문장 
	*ptr2 = 10.99;

	printf("%9g %9g \n", num, **dptr);

	return 0;

}
이중포인터 매개변수 - ijungpointeo maegaebyeonsu

ptr2 = *dptr; 이 실행되면 포인터 변수의 참조관계는 다음과 같다.

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

그리고 변수 num에 접근하는 방법은 다음과 같이 총 4가지

**dptr = 10.1;

*ptr    = 20.2;

*ptr2  = 30.3;

num   = 40.4

포인터 변수 대상의 Call-by-reference

일전에 두 변수에 저장된 값을 서로 바꿔서 저장하는 함수를 다음과 같이 정의한 바 있다.

void swap(int* ptr1, int* ptr2)
{
	int temp = *ptr1;
	*ptr1 = *ptr2;
	*ptr2 = temp;
}

이번에는 두 싱글 포인터 변수에 저장된 값을 서로 바꿔서 저장하는 함수를 정의

아래와 같이 포인터 변수의 참조 관계를 바꿔준다.

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

아래 결과는 제대로 만들어내지 못한다.

#include <stdio.h>

void swap(int*, int*);


int main(void)
{
	int num1 = 10, num2 = 20;
	int* ptr1, * ptr2;
	ptr1 = &num1, ptr2 = &num2;
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);

	swap(ptr1, ptr2);
	printf("*ptr1, *ptr2 : %d %d \n", *ptr1, *ptr2);

}

void swap(int* p1, int* p2)
{
	int* temp = p1;
	p1 = p2;
	p2 = temp;
}
이중포인터 매개변수 - ijungpointeo maegaebyeonsu

왜냐

"p1은 ptr1과 별개이고 p2는 ptr2와 별개이기 때문에"

다음의 상황과 같음(함수가 실행되지 않고 매개변수에 값만 전달된 상황)

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

위 swap 함수에 인자 전달 시 아래와 같은 상황(값 변경 전)

swap 함수 몸체부분이 실행되면 p1과 p2에 저장된 값이 바뀌어 다음의 상태

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

p1과 p2에 저장된 값은 바뀌지만 ptr1과 ptr2와는 별개의 변수이기 때문에 ptr1은 여전히 num1의 주소 값을, ptr2는 여전히 num2의 주소 값을 저장하는 상태

≫ 보면 여전히 0x10은 10을 가리키고, 0x20은 20을 가리킨다.

그렇다면 어떻게 해야 "함수 내에서" 가리키는 대상을 바꿀 수 있음?

이를 위해서는 함수내에서 변수 ptr1과 ptr2에 직접 접근이 가능해야 함.

그래서 이 두 변수에 저장된 값을 서로 바꿔줘야 함

이를 위해서는 int형 더블 포인터가 매개변수로 선언

#include <stdio.h>

void swap(int*, int*);


int main(void)
{
	int num1 = 10;
	int num2 = 20;

	int* ptr1;
	int* ptr2;

	ptr1 = &num1;
	ptr2 = &num2;

	printf("*ptr1, *ptr2: %d %d \n", *ptr1, *ptr2);

	swap(&ptr1, &ptr2);

	printf("*ptr1, *ptr2: %d %d \n", *ptr1, *ptr2);

}

void swap(int** dp1, int** dp2)
{
	int* temp = *dp1;
	*dp1 = *dp2;
	*dp2 = temp;
}
이중포인터 매개변수 - ijungpointeo maegaebyeonsu

동작원리

swap(&ptr1, &ptr2)

위 코드를 보면 이전과 달리 주소 값이 전달 되었다.

그렇다면 ptr1과 ptr2의 주소 값이 swap의 매개변수로 전달이 되어서 다음의 형태가 된다.

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

그 다음 세 문장이 실행

int *temp = *dp1;
*dp1 = *dp2;
*dp2 = temp;

dp1이 가리키는 변수와 dp2가 가리키는 변수에 저장된 값을 서로 교환하는 코드

*dp1은 ptr1을 의미하고 *dp2는 ptr2를 의미함

int *temp = ptr1;
 ptr1 = ptr2;
ptr2 = temp;

이를 실행하면 다음과 같이 값이 바뀐다.

이중포인터 매개변수 - ijungpointeo maegaebyeonsu

포인터 배열과 포인터 배열의 포인터 형

int* arr[20];         // 길이가 20인 int형 포인터 배열 arr1

double* arr2[30];  // 길이가 30인 double형 포인터 배열 arr2

int arr[30];          // 배열이름 arr은 int형 포인터

  • 1차원 배열이름이 가리키는 요소의 자료형만 고려하면 됨

그렇다면 위에 선언된 int형 포인터 배열 의 이름 arr1과 double형 포인터 배열의 arr2의 포인터 형은?

1차원 배열이기 때문에 배열이름이 가리키는 첫 번째 요소의 자료형에 따라서 포인터 형이 결정된다.

즉, arr1이 가리키는 첫 번째 요소 →→ int형 싱글 포인터

따라서, 배열이름 arr1은 int형 더블 포인터

예시

#include <stdio.h>

int main(void)
{
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;
	int* ptr1 = &num1;
	int* ptr2 = &num2;
	int* ptr3 = &num3;

	int* ptrArr[] = { ptr1, ptr2, ptr3 };
	int** dptr = ptrArr; // dprArr과 dptr의 포인터 형이 일치함을 뜻함

	printf("%d %d %d \n", *(ptrArr[0]), *(ptrArr[1]), *(ptrArr[2]));
	printf("%d %d %d \n", *(dptr[0]), *(dptr[1]), *(dptr[2]));

	return 0;

}
이중포인터 매개변수 - ijungpointeo maegaebyeonsu

헷갈려서 정리...

  • ptrArr[]의 배열을 새롭게 정의할 때 앞에 * 연산자를 붙여 배열을 정의했다.
  • 그 이유는 ptr1, ptr2, ptr3의 각 요소가 포인터이기 때문에 포인터형 배열을 선언한 것으로 볼 수 있다.
  • 그냥 일반 배열은 int arr[3] = { 1, 2, 3 } 이렇게 배열의 요소int 자료형들이 있기 때문에 그냥 int arr[3]으로 정의가 가능하지만
  • 새롭게 정의한 배열을 가리키는 포인터 변수로 ptrArr[]로 정의하였다. 즉 ptrArr[]은 ptr1, ptr2, ptr3 모두를 가리킨다.
  • 그렇다면 위 저자의 설명처럼 "배열이름이 가리키는 첫 번째 요소의 자료형에 따라서 포인터 형이 결정"된다고 하였다.
  • 그렇다면 ptrArr[]의 첫 번째 요소는 무엇인가 → 바로 num1을 가리키는 포인터 변수인 ptr1이다. 
  • 따라서 num1을 가리키는 ptr(싱글 포인터)들을 담고있는 ptrArr을 가리키는 dptr은 더블 포인터 형태로 정의한다.

쉽게 생각해서 싱글 포인터 변수를 다시 가리키는 상황이 있으면 더블 포인터로 정의한다고 생각하면 되지 않을까 싶다

참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.