목차
이중 포인터와 배열 포인터
이중 포인터 개념
- 이중 포인터는 포인터의 주소를 저장하는 포인터로, **ptr
과 같이 선언되며, 포인터의 포인터.
#include <stdio.h>
int main(void)
{
int a = 10; // int형 변수의 선언과 초기화
int* pi; // 포인터 선언
int** ppi; // 이중 포인터 선언
pi = &a; // int형 변수의 주소를 저장한 포인터
ppi = π // 포인터의 주소를 저장한 이중 포인터
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
----------------------------------------------------
![](https://blog.kakaocdn.net/dn/7DZ5O/btsGsiZl1b7/QuBpFpK0QciSEmLHva1XV1/img.png)
![](https://blog.kakaocdn.net/dn/cr7pWy/btsGq99EeJc/wso2JN4Esa2EDdGYQpbf61/img.png)
주소 1218885968 | ➡️ | 주소 1218885976 | ➡️ | 주소 1218885988 |
1218885976 | 1218885988 | 10 | ||
이중포인터 ppi | 포인터pi | 변수 a |
규칙
- 포인터를 변수면으로 쓰면 그 안의 값이 된다.
- 포인터에 & 연산을 하면 포인터 변수의 주소가 된다.
- 포인터의 *연산은 화살표 ➡️ 를 따라간다.
이중 포인터의 형태
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; // 주소를 포인터에 저장
π // 포인터에 주소 연산자를 써서 주소를 구할 수 있음.
&(&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;
}
이 코드는 두 개의 문자열 포인터 pa
와 pb
가 가리키는 문자열을 서로 바꾸는 기능을 수행한다.
(swap_ptr
함수를 이용하여 포인터가 가리키는 대상을 서로 교환)
- 초기에
pa
포인터는"success"
문자열을 가리키고,pb
포인터는"failure"
문자열을 가리킵니다. swap_ptr
함수는 포인터의 포인터(char**
)를 매개변수로 받아, 두 포인터가 가리키는 대상을 서로 바꿉니다.- 함수 호출 후,
pa
는"failure"
를,pb
는"success"
를 가리키게 됩니다.
코드 실행 과정
단 계 |
코드 |
|
|
설명 |
---|---|---|---|---|
1 | 초기 상태 | “success” | “failure” | pa 와 pb 가 각각 “success”, “failure” 문자열을 가리킵니다. |
2 | swap_ptr(&pa, &pb); 호출 |
“failure” | “success” | swap_ptr 함수를 통해 pa 와 pb 가 가리키는 값이 서로 바뀝니다. |
함수 swap_ptr
의 작동 원리
- 입력:
char** ppa
,char** ppb
(두 문자열 포인터의 주소) - 과정:
- 임시 포인터
pt
를 선언합니다. pt
에*ppa
의 값(즉,pa
가 가리키는 문자열의 주소)을 저장합니다.*ppa
(즉,pa
가 가리키는 곳)에*ppb
의 값(즉,pb
가 가리키는 문자열의 주소)을 저장합니다.*ppb
(즉,pb
가 가리키는 곳)에pt
의 값(원래pa
가 가리키던 문자열의 주소)을 저장합니다.
- 임시 포인터
- 결과:
ppa
와ppb
가 가리키는 포인터의 대상이 서로 바뀝니다.
이 과정을 통해, 두 포인터 pa
와 pb
가 처음에 각각 가리키고 있던 문자열의 대상이 서로 교환되어, 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
규칙
- 배열 전체가 하나의 논리적인 변수
- 배열의 주소에 정수를 더하면 배열 전체의 크기를 곱해서 더함.
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()
가 이에 해당합니다.