본문 바로가기
프로그래밍/C\C++

C/C++ 포인터(pointer) 내용 정리

by 허구의 2025. 6. 19.
728x90

 

C++ 포인터: 단순한 주소가 아닌 메모리 제어의 핵심

포인터는 단순히 "주소를 저장하는 변수"로 시작하지만, C++에서는 이보다 훨씬 깊은 의미를 가집니다. 이번 글에서는 포인터의 기본을 넘어, 메모리 모델, 포인터 산술, const 포인터, 함수 포인터, 동적 할당 관리까지 보다 심도 있게 다룹니다.

포인터는 메모리 모델 그 자체

C++은 하드웨어와 밀접하게 맞닿아 있는 언어입니다. 포인터를 이해한다는 것은 결국 메모리의 구조 자체를 이해한다는 것과 같습니다. 모든 변수는 물리적 메모리 어딘가에 주소를 가지고 있고, 포인터는 그 주소를 직접 다룰 수 있게 합니다.

int x = 42;
int* p = &x;

 

위에서 p는 스택 메모리 상의 변수 x의 주소를 저장합니다. 이 주소를 역참조(*p)하면 x의 값을 읽거나 수정할 수 있습니다.

포인터 산술(pointer arithmetic)

포인터는 단순히 주소만 저장하는 것이 아니라, 자료형을 알고 있는 주소입니다. 그래서 포인터 간 연산은 단순 주소값 계산이 아니라 타입에 따라 자동으로 크기를 고려합니다.

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;  // 배열 이름은 첫 원소 주소
p++;           // 다음 int로 이동 (주소 + sizeof(int))

 

이 때 p++는 실제 주소값에 4바이트(int 크기)만큼 증가하는 연산입니다. 포인터 산술은 정적 배열뿐 아니라 동적 메모리에서도 강력한 순회 도구가 됩니다.

const 포인터와 포인터 상수

const의 위치에 따라 포인터의 의미는 달라집니다:

  • const int* p: 가리키는 값을 수정 불가 (읽기 전용 데이터)
  • int* const p: 포인터가 다른 주소를 가리키는 것 불가 (고정된 주소)
  • const int* const p: 값도 수정 불가, 주소도 변경 불가 (완전 상수화)

동적 메모리와 포인터

정적 메모리(스택)를 넘어서는 유연성을 제공하는 것이 동적 메모리 할당입니다.

int* p = new int(42);
*p = 100;
delete p;

 

new로 할당한 메모리는 delete로 반드시 해제해야 합니다. 이 과정을 생략하거나 실수하면 메모리 누수(leak)가 발생합니다.

C++11 이후 등장한 스마트 포인터는 이런 관리 부담을 크게 줄여줍니다.

std::unique_ptr<int> p = std::make_unique<int>(42);

 

스마트 포인터는 포인터를 객체처럼 관리하여 RAII 패턴에 따라 메모리 안전성을 크게 높여줍니다.

함수 포인터: 코드 주소도 포인터다

포인터는 데이터뿐 아니라 함수의 주소도 가리킬 수 있습니다.

int add(int a, int b) { return a + b; }
int (*fp)(int, int) = add;
int result = fp(3, 4);  // 함수 호출

 

함수 포인터는 콜백 구현, 테이블 기반 디스패치 등에 유용하게 활용됩니다. 특히 STL의 std::function과 결합하면 보다 현대적인 형태로 일반화할 수 있습니다.

다중 포인터 (이중 포인터)

포인터를 가리키는 포인터도 가능합니다:

int x = 10;
int* p = &x;
int** pp = &p;

**pp = 20;  // 결국 x의 값을 바꿈

 

다중 포인터는 다차원 동적 배열, 복잡한 자료구조, 라이브러리 함수 인터페이스 등에서 종종 활용됩니다.

포인터의 위험성과 강력함

  • Dangling Pointer: 삭제된 메모리를 계속 참조
  • Memory Leak: 할당 후 해제를 잊음
  • Buffer Overflow: 배열 경계 초과 접근

포인터는 C++의 가장 위험한 도구 중 하나입니다. 하지만 동시에, 이 저수준 제어력을 통해 C++이 여전히 시스템 프로그래밍 최강의 언어로 남아있게 합니다.

 

감사합니다.

728x90