포인터 기초
포인터의 개념
포인터는 메모리 주소를 저장하는 변수입니다.
이를 통해 간접적으로 데이터에 접근하고 조작할 수 있습니다. 포인터는 동적 메모리 할당,
효율적인 데이터 구조 구현, 함수에 인자 전달 등 다양한 상황에서 중요한 역할을 합니다.
포인터 선언과 초기화
포인터는 다음과 같이 선언합니다.
int* ptr; // int 타입을 가리키는 포인터
char* cptr; // char 타입을 가리키는 포인터
double* dptr; // double 타입을 가리키는 포인터
int x = 10;
int* ptr = &x; // x의 주소를 ptr에 저장
// 또는
int* ptr2 = nullptr; // 널 포인터로 초기화 (C++11 이후)
주소 연산자 &
와 역참조 연산자 *
- 주소 연산자
&
: 변수의 메모리 주소를 얻습니다. - 역참조 연산자
*
: 포인터가 가리키는 메모리 위치의 값에 접근합니다.
#include <iostream>
int main() {
int x = 10;
int* ptr = &x;
std::cout << "x의 값: " << x << std::endl;
std::cout << "x의 주소: " << ptr << std::endl;
std::cout << "ptr이 가리키는 값: " << *ptr << std::endl;
*ptr = 20; // x의 값을 간접적으로 변경
std::cout << "변경 후 x의 값: " << x << std::endl;
return 0;
}
포인터와 배열
배열 이름은 첫 번째 요소의 주소를 나타냅니다.
#include <iostream>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // p는 arr[0]의 주소를 가짐
for(int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "] = " << *p << std::endl;
p++; // 다음 요소로 이동
}
return 0;
}
포인터 연산
포인터에 정수를 더하거나 빼면 해당 타입의 크기만큼 주소가 변경됩니다.
int* p = arr;
p++; // 다음 정수의 주소로 이동 (4바이트 증가)
p += 2; // 2개의 정수만큼 이동 (8바이트 증가)
널 포인터
널 포인터는 유효하지 않은 메모리 주소를 나타냅니다.
int* ptr = nullptr; // C++11 이후
// int* ptr = NULL; // C++11 이전 (하위 호환성을 위해 여전히 사용 가능)
if (ptr == nullptr) {
std::cout << "ptr은 널 포인터입니다." << std::endl;
}
void 포인터
void 포인터는 모든 타입의 포인터로 변환될 수 있는 일반적인 포인터 타입입니다.
void* vptr;
int x = 10;
vptr = &x; // 허용됨
// 사용 시 적절한 타입으로 캐스팅 필요
int* iptr = static_cast<int*>(vptr);
const와 포인터
const와 포인터의 조합은 다음과 같습니다.
int x = 10, y = 20;
const int* p1 = &x; // 포인터가 가리키는 값을 변경할 수 없음
// *p1 = 30; // 오류
p1 = &y; // 허용됨
int* const p2 = &x; // 포인터 자체를 변경할 수 없음
*p2 = 30; // 허용됨
// p2 = &y; // 오류
const int* const p3 = &x; // 둘 다 변경할 수 없음
// *p3 = 30; // 오류
// p3 = &y; // 오류
포인터의 위험성
- 댕글링 포인터 : 해제된 메모리를 가리키는 포인터
- 메모리 누수 : 할당된 메모리를 해제하지 않음
- 버퍼 오버플로우 : 배열의 범위를 벗어난 접근
예방 방법
- 포인터 사용 후 nullptr로 설정
- 스마트 포인터 사용
- 배열 접근 시 범위 검사
스마트 포인터 소개 (C++ 11 이후)
C++ 11부터 도입된 스마트 포인터는 메모리 관리를 자동화합니다.
std::unique_ptr
: 단일 소유권 포인터std::shared_ptr
: 공유 소유권 포인터std::weak_ptr
:std::shared_ptr
의 순환 참조 문제 해결
간단한 예
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> uptr = std::make_unique<int>(10);
std::cout << "uptr 값: " << *uptr << std::endl;
std::shared_ptr<int> sptr = std::make_shared<int>(20);
std::cout << "sptr 값: " << *sptr << std::endl;
return 0;
}
연습 문제
- 정수형 포인터를 사용하여 두 변수의 값을 교환하는 함수를 작성하세요.
- 문자열(C-style string)의 길이를 계산하는 함수를 포인터를 사용하여 구현하세요.
- 정수 배열을 가리키는 포인터를 인자로 받아, 배열의 모든 요소를 출력하는 함수를 작성하세요.
- void 포인터를 사용하여 어떤 타입의 변수든 그 값을 출력할 수 있는 범용 출력 함수를 구현하세요.
참고자료
- C++ 공식 문서의 포인터 섹션 : Pointer declarations
- "Effective C++" by Scott Meyers (항목 13 : 자원 관리에는 객체가 그만!)
- "C++ Primer" by Stanley B. Lippman, Josée Lajoie, and Barbara E. Moo (Chapter 2 : Variables and Basic Types)
- C++ Core Guidelines의 포인터 관련 규칙 : ES.42 : Keep pointers to members simple and direct
- "A Tour of C++" by Bjarne Stroustrup (Chapter 13 : Resource Management)