본문 바로가기
카테고리 없음

C 언어 고급편]🚀 응용 포인터

by redcubes 2024. 4. 7.

목차

이중 포인터와 배열 포인터

이중 포인터 개념

- 이중 포인터는 포인터의 주소를 저장하는 포인터로, **ptr과 같이 선언되며, 포인터의 포인터.

#include <stdio.h>

int main(void)
{
	int a = 10;		// int형 변수의 선언과 초기화
	int* pi;		// 포인터 선언
	int** ppi;		// 이중 포인터 선언

	pi = &a;		// int형 변수의 주소를 저장한 포인터
	ppi = &pi;		// 포인터의 주소를 저장한 이중 포인터

	printf("----------------------------------------------------\n");
	printf("변수      변숫값       &연산       *연산      **연산\n");
	printf("----------------------------------------------------\n");
	printf("   a%12d%12u\n", a, &a);
	printf("  pi%12u%12u%12d\n", pi, &pi, *pi);
	printf(" ppi%12u%12u%12u%12u\n", ppi, &ppi, *ppi, **ppi);
	printf("----------------------------------------------------\n");

	return 0;
}

실행결과

----------------------------------------------------
변수      변숫값       &연산       *연산      **연산
----------------------------------------------------
   a          10  1218885988
  pi  1218885988  1218885976          10
 ppi  1218885976  1218885968  1218885988          10
----------------------------------------------------
주소 1218885968 ➡️ 주소  1218885976 ➡️ 주소  1218885988
1218885976 1218885988 10
이중포인터 ppi 포인터pi 변수  a

 규칙

  1. 포인터를 변수면으로 쓰면 그 안의 값이 된다.
  2. 포인터에 & 연산을 하면 포인터 변수의 주소가 된다.
  3. 포인터의 *연산은 화살표 ➡️ 를 따라간다.

 

이중 포인터의 형태

int **ptr;

위 코드에서 ptr은 이중 포인터로, int형 포인터의 주소를 저장합니다.

포인터가 가리키는 것의 자료형과 포인터 자신의 형태를 구분

int형 변수의 주소를 저장하는 포인터는 가리키는 자료형은 int 형 포인터 자신의 자료형은 (int *) 형

이중 포인터도 가리키는 포인터의 형태에 맞춰 선언해야 함. 이중 포인터를 선언할 때는 이중 포인터에 저장할 주소가 어떤 포인터형의 주소인지를 먼저 파악해야 함.

double a = 3.5;
double *pi = &a;
ppi   pi   a
300 301 302 303 ➡️ 200 201 202 203 ➡️ 100 101 102 103 104 105 106 107
200   100   3.5
(double **)형   (double *)형   double형

주소와 포인터의 차이

  • 주소 상수: 상수인 주소에는 주소 연산자를 쓸 수 없다.
  • 포인터: 주소를 저장하는 변수.  주소 연산자를 쓸 수 있다.
int a;
int *pi = &a; // 주소를 포인터에 저장
&pi;          // 포인터에 주소 연산자를 써서 주소를 구할 수 있음.
&(&a);        // 상수인 주소상수(값)에는 주소가 없다.

다중 포인터

다중 포인터는 이중 포인터 이상의, 더 많은 수준의 간접 참조. 예를 들어, ***ptr은 삼중 포인터.

사중 이상도 가능하나 가독성이 떨어져 가능한 한 사용하지 않는 편이 좋음.

이중 포인터 활용 1: 포인터 값을 바꾸는 함수의 매개변수

이중 포인터를 사용하여 함수 내에서 포인터 변수의 값을 변경할 수 있습니다.

#include <stdio.h>

void swap_ptr(char** ppa, char** ppb);

int main(void)
{
	char* pa = "success";
	char* pb = "failure";

	printf("pa -> %s, pb -> %s\n", pa, pb);   // 바꾸기 전에 문자열 출력
	swap_ptr(&pa, &pb);                       // 함수 호출
	printf("pa -> %s, pb -> %s\n", pa, pb);   // 바꾼 후에 문자열 출력

	return 0;
}

void swap_ptr(char** ppa, char** ppb)
{
	char* pt;

	pt = *ppa;
	*ppa = *ppb;
	*ppb = pt;
}

이 코드는 두 개의 문자열 포인터 papb가 가리키는 문자열을 서로 바꾸는 기능을 수행한다.
(swap_ptr 함수를 이용하여 포인터가 가리키는 대상을 서로 교환)

  1. 초기에 pa 포인터는 "success" 문자열을 가리키고, pb 포인터는 "failure" 문자열을 가리킵니다.
  2. swap_ptr 함수는 포인터의 포인터(char**)를 매개변수로 받아, 두 포인터가 가리키는 대상을 서로 바꿉니다.
  3. 함수 호출 후, pa"failure"를, pb"success"를 가리키게 됩니다.

코드 실행 과정


코드

pa
가리키는 값

pb
가리키는 값

설명
1 초기 상태 “success” “failure” papb가 각각 “success”, “failure” 문자열을 가리킵니다.
2 swap_ptr(&pa, &pb); 호출 “failure” “success” swap_ptr 함수를 통해 papb가 가리키는 값이 서로 바뀝니다.

함수 swap_ptr의 작동 원리

  • 입력: char** ppa, char** ppb (두 문자열 포인터의 주소)
  • 과정:
    1. 임시 포인터 pt를 선언합니다.
    2. pt*ppa의 값(즉, pa가 가리키는 문자열의 주소)을 저장합니다.
    3. *ppa (즉, pa가 가리키는 곳)에 *ppb의 값(즉, pb가 가리키는 문자열의 주소)을 저장합니다.
    4. *ppb (즉, pb가 가리키는 곳)에 pt의 값(원래 pa가 가리키던 문자열의 주소)을 저장합니다.
  • 결과: ppappb가 가리키는 포인터의 대상이 서로 바뀝니다.

이 과정을 통해, 두 포인터 papb가 처음에 각각 가리키고 있던 문자열의 대상이 서로 교환되어, pa"failure"를, pb"success"를 가리키게 됩니다. 이는 포인터의 포인터를 이용하여 두 변수의 값을 간접적으로 교환하는 방식으로 이루어집니다.

 

이중 포인터 활용 2: 포인터 배열을 매개변수로 받는 함수

함수가 포인터 배열을 매개변수로 받아 처리할 때 이중 포인터를 사용합니다.

#include <stdio.h>

void print_str(char** pps, int cnt);

int main(void)
{
	char* ptr_ary[] = { "eagle", "tiger", "lion", "squirrel" };   // 초기화
	int count;                        // 배열 요소 수를 저장할 변수

	count = sizeof(ptr_ary) / sizeof(ptr_ary[0]);  // 배열 요소의 수 계산
	print_str(ptr_ary, count);        // 배열명과 배열 요소 수를 주고 호출

	return 0;
}

void print_str(char** pps, int cnt)   // 매개변수로 이중 포인터 사용
{
	int i;

	for (i = 0; i < cnt; i++)         // 배열 요소 수만큼 반복
	{
		printf("%s\n", pps[i]);       // 이중 포인터를 배열명처럼 사용
	}
}

배열 요소의 주소와 배열의 주소

배열의 이름은 배열의 첫 번째 요소의 주소와 동일합니다. arr&arr[0]은 같은 주소를 가리킵니다.

그런데 배열 전체를 하나의 변수로 생각하고 주소를 구한다면?

&arr
#include <stdio.h>

int main(void)
{
	int ary[5];

	printf("  ary의 값 : %u\t", ary);        // 주소로서의 배열명의 값
	printf("ary의 주소 : %u\n", &ary);       // 배열의 주소
	printf("   ary + 1 : %u\t", ary + 1);
	printf("  &ary + 1 : %u\n", &ary + 1);

	return 0;
}
ary의 값 : 281410160  ary의 주소 : 281410160
ary + 1 : 281410164    &ary + 1 : 281410180

&ary는 배열 전체를 가리키므로 대상의 크기가 20

ary는 배열 첫 요소를 가리키므로 대상의 크기가 4

규칙

  1. 배열 전체가 하나의 논리적인 변수
  2. 배열의 주소에 정수를 더하면 배열 전체의 크기를 곱해서 더함.

 

2차원 배열과 배열 포인터

2차원 배열은 행과 열로 구성된 배열입니다. 배열 포인터는 이러한 배열의 행을 가리키는 포인터입니다.

#include <stdio.h>

void print_ary(int(*)[4]);

int main(void)
{
	int ary[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };

	print_ary(ary);				// 배열명을 인수로 주고 함수 호출

	return 0;
}

void print_ary(int(*pa)[4])		// 매개변수는 배열 포인터
{
	int i, j;

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%5d", pa[i][j]);  // pa를 2차원 배열처럼 사용
		}
		printf("\n");
	}
}

2차원 배열 요소의 2가지 의미

  • arr[i][j]: 2차원 배열의 i행 j열 요소
  • *(*(arr + i) + j): 포인터 연산을 통해 동일한 요소에 접근

2차원 배열의 요소를 참조하는 원리

2차원 배열의 요소는 연속적인 메모리 공간에 저장되며, 행 우선 순서로 배치됩니다.

함수 포인터와 void 포인터

함수 포인터의 개념

함수 포인터는 함수의 주소를 저장하는 포인터입니다. 함수를 매개변수로 전달하거나 콜백 함수로 사용할 때 활용됩니다.

함수의 형태

void (*func_ptr)(int);

위 코드는 정수를 매개변수로 받고 반환값이 없는 함수의 포인터를 선언합니다.

함수 포인터의 활용

함수 포인터를 사용하여, 함수를 다른 함수의 인자로 전달하거나, 실행 시점에 호출할 함수를 결정할 수 있습니다.

void 포인터

void *는 C나 C++ 프로그래밍 언어에서 “void 포인터” 또는 "범용 포인터"로 불립니다. 이는 특정 데이터 타입이 지정되지 않은 포인터를 의미하여, 어떠한 타입의 객체도 가리킬 수 있습니다. 하지만, void * 포인터를 통해 직접 데이터에 접근하거나 수정하려면, 사용하려는 데이터의 타입으로 명시적으로 형 변환을 해야 합니다. 이러한 특성 때문에 void *는 다양한 타입의 데이터를 처리해야 하는 라이브러리 함수들에서 많이 사용됩니다. 예를 들어, C 표준 라이브러리 함수인 qsort()bsearch()가 이에 해당합니다.

 

https://gall.dcinside.com/board/view/?id=dcbest&no=136639
https://gall.dcinside.com/board/view/?id=dcbest&no=136639