[C언어] 포인터의 포인터

김민정·2024년 8월 28일
0
post-thumbnail

Chapter 17. 포인터의 포인터

17-1 "포인터의 포인터에 대한 이해"

포인터의 포인터는 포인터 변수를 가리키는 또 다른 포인터 변수를 뜻하는 것으로 흔희 이중 포인터 또는 더블 포인터라 부른다.
* 연산자를 두개 이어서 이중 포인터를 선언할 수 있다.

int ** dptr;	// int형 이중 포인터

*이중 포인터 변수 (더블 포인터 변수)

이중 포인터 변수 (더블 포인터 변수)란? 포인터 변수를 가리키는 포인터 변수이다.
포인터 변수는 종류에 상관없이 무조건 주소 값을 저장하는 변수이다.
다만 차이가 나는 것은 포인터 변수가 가리키는 대상일 뿐이다.

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

위에서 변수 num과 포인터 변수 ptr의 공통점과 차이점은 무엇일까?

  • 공통점 : 둘 다 변수. 값의 저장 가능
  • 차이점 : 저장하는 값의 종류(유형)이 다름

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

double ** dptr = &ptr;

따라서, 변수 num과 싱글 포인터 변수 ptr, 더블 포인터 변수 dptr의 관계는 다음 그림과 같다.

(자, 여기서부터 ㅈㄴ헷갈린다)
위 그림과 같은 상태에서는 dtpr을 대상으로 다음과 같은 방식으로 포인터 변수 ptr과 변수 num에 접근이 가능하다.

*dptr = ...;	// *dptr은 포인터 변수 ptr을 의미함.
*(*dptr) = ...;	// *(*dptr)은 변수 num을 의미함.

*(*dptr)에서 괄호는 생략 가능하기 때문에 **dptr로 표현이 가능하다.
예제를 통해 좀 더 자세히 이해해보자.

  • 예제 (DoublePointerAccess.c)
#include <stdio.h>

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

	printf("%16p %16p \n", ptr, *dptr);
	printf("%16g %16g \n", num, **dptr);
	ptr2 = *dptr;	// ptr2 = ptr과 같은 문장
	*ptr2 = 10.99;
	printf("%16g %16g \n\n", num, **dptr);
    return 0;
}

> 출력
0000006E3B79F368 0000006E3B79F368
            3.14             3.14
           10.99            10.99

위 변수들의 참조 관계는 다음과 같다.

따라서 변수 num에 접근하는 방법은 다음과 같이 총 4가지가 존재한다.

**dptr = 10.1;	// 변수 num에 10.1 저장
*ptr = 20.2;	// 변수 num에 20.2 저장
*ptr2 = 30.3;	// 변수 num에 30.3 저장
num = 40.4;		// 변수 num에 40.4 저장

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

포인터 변수의 참조관계를 바꿔주는 함수를 정의해보자.
두 가지 예제가 제시될 텐데 첫 번째 예시는 제대로 바꿔지지 않는다.
문제점을 파악해 보자.

  • 예제1 (PointerSwapFail.c)
#include <stdio.h>

void SwapIntPtr(int* p1, int* p2)
{
	int* temp = p1;
	p1 = p2;
	p2 = temp;
}

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

	SwapIntPtr(ptr_1, ptr_2);
	printf("*ptr1, *ptr2: %d %d \n\n", *ptr_1, *ptr_2);
	return 0;
}

> 출력
*ptr1, *ptr2: 10 20
*ptr1, *ptr2: 10 20
  • 예제2 (PointerSwapSuccess.c)
#include <stdio.h>

void SwapIntPtrS(int** dp1, int** dp2)
{
	int* temp = *dp1;
	*dp1 = *dp2;
	*dp2 = temp;
}

int main()
{
	int Num1 = 10, Num2 = 20;
	int* Ptr1, * Ptr2;
	Ptr1 = &Num1, Ptr2 = &Num2;
	printf("*ptr1, *ptr2: %d %d \n", *Ptr1, *Ptr2);

	SwapIntPtrS(&Ptr1, &Ptr2);	// 주소 값 전달.
	printf("*ptr1, *ptr2: %d %d \n\n", *Ptr1, *Ptr2);
    return 0;
}

> 출력
*ptr1, *ptr2: 10 20
*ptr1, *ptr2: 20 10

예제 1의 경우

함수 실행에 도착함수 실행 이후

함수가 실행되면서 p1과 p2에 저장된 값은 변경이 되지만 이는 ptr_1과 ptr_2와는 별개의 변수이기 때문에
여전히 ptr_1은 num1을, ptr_2는 num2를 가리켜 가리키는 대상이 변경은 그대로이다.

반면, 예제 2의 경우

함수 실행에 도착함수 실행 이후

함수에 ptr1과 ptr2의 주소 값이 전달된다.
ptr과 ptr의 주소 값이 함수의 매개변수로 전달되어 가리키는 것이 동일해진다.
함수 내의 코드가 이해가 잘 안간다면 아래와 같이 이해해도 좋다.

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

덕분에 ptr1과 ptr2가 가리키는 주소 값이 변경되어 우리가 원하던 바가 되었다.

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

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

arr1이 가리키는 첫 번째 요소는 int형 싱글 포인터이니, 배열이름 arr1은 int형 더블 포인터가 된다.
arr2가 가리키는 첫 번째 요소는 double형 싱글 포인터이니, 배열이름 arr2는 double형 더블 포인터가 된다.

  • 예제 (PointerArrayType.c)
#include <stdio.h>

int main()
{
	int n1 = 10, n2 = 20, n3 = 30;
	int* pt1 = &n1;
	int* pt2 = &n2;
	int* pt3 = &n3;

	int* ptrArr[] = { pt1,pt2,pt3 };
	int** dpt = ptrArr;
	
	printf("%d %d %d \n", *(ptrArr[0]), *(ptrArr[1]), *(ptrArr[2]));
	printf("%d %d %d \n\n", *(dpt[0]), *(dpt[1]), *(dpt[2]));
	return 0;
}

> 출력
10 20 30
10 20 30

17-2 "다중 포인터 변수와 포인터의 필요성"

다중 포인터란? 포인터 변수의 선언에 있어서 * 연산자가 둘 이상 사용되어 선언되는 포인터 변수를 가리킨다. 포인터 변수 선언 시 * 연산자는 얼마든지 추가될 수 있다.

삼중 포인터

int **tptr;

삼중 포인터는 이중 포인터(더블 포인터)를 가리키는 포인터로 이해할 수 있다.
따라서 이중 포인터 변수의 주소 값을 저장하는 용도로 사용할 수 있다.

  • 예제 (TriplePointer.c)
#include <stdio.h>
int main()
{
	int num = 100;
	int* ptr = &num;
	int** dptr = &ptr;
	int*** tptr = &dptr;

	printf("%d %d \n\n", **dptr, ***tptr);
	return 0;
}

> 출력
100 100

<Review>

포인터의 포인터...
쉽지 않다...

포인터 자체가 어떤 변수를 가리켜 그 변수의 주소값을 나타낸다고 이해하면 된다.
그 가리키고 있는 포인터를 또 다른 포인터가 그 포인터 위치를 가리키고 있다고 생각하면된다. (그리고 그 주소 값을 저장하고 있고,,,)
변수 자체는 변수에 초기화된 값이 있어
포인터를 통해 그 변수 값에 접근할 수 있다 생각하면 된다!
간단한듯 안 간단한듯,,,!😂

다음은 다차원 배열과 포인터의 관계다... 더 화이팅!!

profile
백엔드 코린이😁

0개의 댓글

관련 채용 정보