이중 포인터(double pointer)는 포인터를 가리키는 포인터로 포인터에 대한 포인터를 의미한다. 이중 포인터는 단일 포인터와 달리, 포인터 변수가 저장된 메모리 주소를 가리키는 포인터이다.
이중 포인터는 C와 C++에서 다양한 목적으로 사용되며, 특히 동적 메모리 할당, 포인터 배열, 함수에서 포인터 값을 변경할 때 자주 사용된다.
이중 포인터는 선언할 때 자료형 뒤에 **
를 붙여 선언하다.
int **ptr;
ptr
은 이중 포인터로, int
형 변수를 가리키는 포인터를 가리키는 포인터.이중 포인터는 다음과 같은 형태로 메모리에 존재한다:
int
형 변수가 있고, 그 변수를 가리키는 포인터가 있음.간단한 예시:
int x = 10; // 정수 변수
int *p = &x; // x를 가리키는 포인터
int **pp = &p; // p를 가리키는 이중 포인터
x
는 정수 값 10을 가짐.p
는 x
의 주소를 가리키며, *p
는 x
의 값을 참조.pp
는 p
의 주소를 가리키며, *pp
는 p
를 참조하고, **pp
는 x
의 값을 참조.#include <stdio.h>
int main() {
int x = 10; // 정수 변수 x
int *p = &x; // x를 가리키는 포인터 p
int **pp = &p; // p를 가리키는 이중 포인터 pp
// 값 출력
printf("x: %d\n", x); // x의 값 출력
printf("*p: %d\n", *p); // p가 가리키는 값, 즉 x의 값 출력
printf("**pp: %d\n", **pp); // pp가 가리키는 포인터가 가리키는 값, 즉 x의 값 출력
// 주소 출력
printf("\n주소 출력:\n");
printf("&x: %p\n", (void*)&x); // x의 주소
printf("p: %p\n", (void*)p); // p가 가리키는 주소 (x의 주소)
printf("&p: %p\n", (void*)&p); // p의 주소
printf("pp: %p\n", (void*)pp); // pp가 가리키는 주소 (p의 주소)
printf("&pp: %p\n", (void*)&pp); // pp의 주소
return 0;
}
x: 10
*p: 10
**pp: 10
주소 출력:
&x: 0x7ffee4bff75c
p: 0x7ffee4bff75c
&p: 0x7ffee4bff758
pp: 0x7ffee4bff758
&pp: 0x7ffee4bff754
&x
: 변수 x
의 메모리 주소를 출력.p
: 포인터 p
가 가리키는 주소, 즉 x
의 주소를 출력.&p
: 포인터 p
자체의 주소를 출력.pp
: 이중 포인터 pp
가 가리키는 주소, 즉 p
의 주소를 출력.&pp
: 이중 포인터 pp
자체의 주소를 출력.printf
에서 %p
형식 지정자%p
는 포인터 주소를 출력할 때 사용하는 형식 지정자.(void*)
로 캐스팅.이중 포인터는 포인터를 가리키는 포인터이다.
이중 포인터는 포인터의 주소를 저장하며, 이를 통해 포인터의 값이나 메모리를 간접적으로 제어할 수 있다. 동적 메모리 할당, 포인터 배열 처리, 함수에서 포인터 값 변경 등 여러 상황에서 유용하게 사용된다.
이중 포인터는 다차원 배열의 동적 메모리 할당에서 자주 사용된다. 예를 들어, 2차원 배열을 동적으로 할당할 때 이중 포인터를 사용한다.
malloc()
: 동적 메모리를 할당하는 함수로, 인자로 받은 크기만큼의 바이트 수에 해당하는 메모리를 힙(heap) 영역에서 할당하고 할당된 메모리의 시작 주소를 반환함.free()
: 동적으로 할당된 메모리를 해제.#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
int **arr;
// 2차원 배열을 위한 메모리 할당
arr = (int **)malloc(rows * sizeof(int *)); // 각 행에 대한 포인터 배열 할당
for (int i = 0; i < rows; i++) {
arr[i] = (int *)malloc(cols * sizeof(int)); // 각 행의 열에 대한 메모리 할당
}
// 배열 초기화 및 출력
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i][j] = i * cols + j;
printf("%d ", arr[i][j]);
}
printf("\n");
}
// 할당된 메모리 해제
for (int i = 0; i < rows; i++) {
free(arr[i]); // 각 행의 메모리 해제
}
free(arr); // 포인터 배열 해제
return 0;
}
// 출력 결과:
0 1 2 3
4 5 6 7
8 9 10 11
arr
: 이중 포인터로서, 동적으로 2차원 배열을 할당하는 데 사용됨.(int **)malloc(rows * sizeof(int *))
: rows
개의 포인터 배열을 위해 메모리를 할당. 각 포인터(배열의 한 행)는 int *
타입을 가짐.malloc()
이 반환한 메모리 주소를 정수 포인터를 가리키는 포인터(int **
)로 캐스팅.(int *)malloc(cols * sizeof(int))
:cols
개의 정수 메모리를 할당하여 실제 2차원 배열을 형성.(int **)
:캐스팅(casting)으로, malloc()
함수가 반환하는 메모리 주소를 이중 포인터(int **
)로 변환하는 것이다.
int **
는 이중 포인터로, 정수형 포인터를 가리키는 포인터를 의미.malloc
은 기본적으로 void *
를 반환하므로, 이를 정수형 포인터를 가리키는 포인터로 캐스팅해줘야 안전하게 사용할 수 있음.malloc(rows * sizeof(int *))
:rows
개의 포인터를 가리키는 메모리 공간을 할당한다. rows
만큼의 포인터 배열을 위한 메모리 공간 확보하는 것이다. 이 배열의 각 요소는 정수 배열의 행을 가리키는 포인터가 된다.
rows
: 할당할 포인터 배열의 크기(배열의 행 수).sizeof(int *)
: int *
의 크기(바이트 수)를 의미하며, 각 배열 요소가 정수형 포인터(int *
)이므로, sizeof(int *)
를 사용해 포인터 크기만큼의 메모를 할당함.이중 포인터는 포인터 배열에 접근할 때 사용될 수 있다. 예를 들어, 문자열 배열을 처리할 때 이중 포인터를 사용할 수 있다.
#include <stdio.h>
int main() {
char *arr[] = {"Hello", "World", "C programming"};
char **ptr = arr;
for (int i = 0; i < 3; i++) {
printf("%s\n", *(ptr + i));
}
return 0;
}
// 출력 결과:
Hello
World
C programming
arr
: 문자열을 가리키는 포인터들의 배열.ptr
: 그 배열을 가리키는 이중 포인터.함수에서 포인터의 값을 변경하려면 이중 포인터가 필요하다. 예를 들어, 동적으로 할당된 메모리를 함수에서 초기화하고, 이를 호출하는 함수에서 참조하고 싶을 때 이중 포인터를 사용할 수 있다.
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int **p) {
*p = (int *)malloc(sizeof(int)); // 동적 메모리를 할당
**p = 100; // 할당된 메모리에 값 100 저장
}
int main() {
int *p = NULL;
allocateMemory(&p); // p의 주소를 넘김
printf("p가 가리키는 값: %d\n", *p);
free(p); // 동적 메모리 해제
return 0;
}
// 출력 결과:
p가 가리키는 값: 100
allocateMemory()
함수는 이중 포인터 int **p
를 받아서 동적 메모리를 할당하고, 그 메모리에 값을 저장.