포인터(Pointer)
포인터는 C++에서 메모리 주소를 저장하는 변수이다. 일반적인 변수는 데이터를 저장하지만 포인터는 다른 변수나 메모리의 주소를 저장하는 데 사용된다.
포인터에 대해 자세히 알아보기 전, 먼저 메모리에 주소 값을 할당하는 과정을 살펴볼 필요가 있다.
우리가 변수를 선언하면 컴파일러는 해당 변수의 테이터 타입에 따라 적절한 크기의 메모리를 할당한다 (이 메모리는 스택(지역 변수) 또는 힙(동적 변수)에 위치한다).
할당 과정
예시:
int num = 10; // int형 변수 선언 및 초기화
- int형 변수를 하나 선언했다면 4byte(시스템에 따라 다름)를 차지하고 변수는 메모리에 저장된다.
주소 값
0x7ffeeabc1234 10
- 메모리에는 10이 저장되고 해당 변수는 특정 주소를 가지게 된다 (ex. 0x7ffeeabc1234).
포인터 선언
다시 포인터로 돌아와보자. 포인터가 특정 변수를 가리키도록 설정하면 해당 변수의 주소 값이 포인터에 할당된다.
선언 방법:
데이터타입* 포인터이름;
데이터타입 *포인터이름;
// 둘 중 하나 사용
이때 주소 연산자(&)로 변수가 저장된 메모리의 주소값을 가져올 수 있고, 이 주소 값은 포인터에 저장하거나 출력할 수 있다.
예시:
int num = 10;
int* ptr = # // num의 주소를 ptr에 저장
cout << # // num의 주소 출력
// 출력 예:
0x7ffeeabc1234
→ 변수 num의 값 10은 메모리 주소 0x7ffeeabc1234에 저장되어 있고, 포인터 ptr은 이 주소 값을 저장한다.
int num = 10;
int* ptr = # // num의 주소를 할당
메모리 구조:
주소 값
0x7ffeeabc1234 10 // num의 값
0x7ffeeabc5678 0x7ffeeabc1234 // ptr이 저장한 num의 주소
이를 좀 더 알아보기 쉽게 그려보면 아래와 같다.

이중 포인터(Double pointer)
이중 포인터는 포인터를 가리키는 포인터이다. 즉, 포인터의 주소를 저장하는 포인터라고 할 수 있다.
선언 방법:
데이터타입** 이중포인터이름;
예시:
int num = 10; // 정수 변수
int* ptr = # // num을 가리키는 포인터
int** dptr = &ptr; // ptr을 가리키는 이중 포인터
메모리 구조:
주소 값
0x100 10 (num의 값)
0x200 0x100 (ptr이 가리키는 주소)
0x300 0x200 (dptr이 가리키는 주소)
이중 포인터를 사용하는 이유?
- 포인터를 수정하고 싶을 때 - 함수 호출 시 포인터 자체를 수정하려면 이중 포인터를 사용하면 된다.
void updatePointer(int** pptr) {
static int num = 20;
*pptr = # // 포인터가 새로운 주소를 가리키도록 수정
}
int main() {
int num = 10;
int* ptr = #
cout << *ptr << endl; // 출력: 10
updatePointer(&ptr);
cout << *ptr << endl; // 출력: 20
}
- 2차원 배열을 동적으로 관리 - 동적 메모리를 사용해 2차원 배열을 생성하고 관리할 때 이중 포인터를 사용한다.
int rows = 3, cols = 4;
int** matrix = new int*[rows];
for (int i = 0; i < rows; ++i) {
matrix[i] = new int[cols];
}
// 메모리 해제
for (int i = 0; i < rows; ++i) {
delete[] matrix[i];
}
delete[] matrix;
- 복잡한 데이터 구조를 다룰 때 - 연결 리스트, 트리, 그래프 등 동적 자료구조에서 노드를 관리할 때 이중 포인터가 유용하다.
역참조
역참조(Dereference)는 포인터가 가리키는 메모리 주소에 저장된 실제 값에 접근하는 것을 의미한다.
포인터는 메모리 주소를 저장하지만 역참조 연산자를 사용하면 해당 주소에 저장된 값을 읽거나 수정할 수 있다.
- C++에서 역참조 연산자는 *를 사용한다.
- 포인터가 가리키는 메모리 주소의 값을 참조한다.
예시:
#include <iostream>
using namespace std;
int main() {
int num = 10; // 정수 변수
int* ptr = # // num의 주소를 저장하는 포인터
cout << "num의 값: " << num << endl; // 출력: 10
cout << "ptr이 가리키는 값: " << *ptr << endl; // 출력: 10
*ptr = 20; // 역참조를 통해 num의 값을 변경
cout << "num의 값: " << num << endl; // 출력: 20
return 0;
}
역참조 연산자와 동적 메모리:
역참조 연산자는 동적 메모리에서 데이터 값을 읽거나 수정할 때도 사용된다.
int* ptr = new int(42); // 동적 메모리 할당
cout << *ptr << endl; // 출력: 42
*ptr = 100; // 값 변경
cout << *ptr << endl; // 출력: 100
delete ptr; // 메모리 해제
ptr = nullptr; // Dangling Pointer 방지
활용:
1. 함수에서 포인터 사용
void increment(int* p) {
(*p)++; // 역참조를 통해 값을 수정
}
int main() {
int num = 10;
increment(&num); // num의 주소를 전달
cout << num; // 출력: 11
return 0;
}
2. 다차원 배열
int matrix[2][2] = {{1, 2}, {3, 4}};
int (*ptr)[2] = matrix; // 2차원 배열 포인터
cout << *(*ptr + 1); // 출력: 2
3. 이중 포인터와 역참조
int num = 10;
int* ptr = #
int** dptr = &ptr;
cout << **dptr << endl; // 출력: 10
→ 역참조는 주로 동적 메모리 관리, 함수 인자 전달, 복잡한 자료구조에서 활용된다. 항상 포인터가 유효한 주소를 가리키고 있는지 확인하면서 역참조를 사용할 필요가 있다.
※ 포인터 사용 시 주의점
- 포인터는 선언한 후 초기화하지 않으면 쓰레기값을 가질 수 있기 때문에 초기화를 무조건 해주어야 한다.
- 동적 메모리 해제 후 포인터 사용 금지(Dangling pointer) - delete 후에도 포인터는 여전히 해당 메모리 주소를 가리키는데, 이를 댕글링 포인터 라고 한다. 해제된 메모리에 접근하면 프로그램 충돌이 발생할 수 있다.
- 다중 포인터 사용 시 주의 - 이중 포인터나 삼중 포인터를 사용할 때 올바르게 초기화 및 해제하지 않으면 복잡한 메모리 문제가 발생할 수 있다.
'개발 > C++' 카테고리의 다른 글
| [C++] 람다 표현식(Lambda Expression) (2) | 2024.12.25 |
|---|---|
| [C++]REVIEW. 메모리 구조 (4) | 2024.12.23 |