본문 바로가기
Game DevTip/C++

10. C++ 포인터 기초 : 메모리 주소의 이해

by LIKE IT.라이킷 2025. 7. 15.

 

포인터란 무엇일까?

포인터는 집 주소와 같은 것, 즉 집에는 고유한 주소가 있듯(서울시 어쩌구)

모든 변수들도 메모리의 저장되므로 주소가 필요하다. 

 

다시 정리해서 말하자면, 메모리 주소는 각 데이터가 저장된 위치의 고유 번호이다. 

포인터는 이 주소를 기억하는 변수(메모장 역할)이다. 

 

그렇다면 주소를 따라가서 실제 데이터를 가지고 올 수 있나? 

라는 생각을 할텐데, 당연히 가능하고 이를 역참조라고 부른다. 

 

메모리 구조 시각화

메모리 주소    변수명      저장된 값
1000         number      42
1004         ptr         1000  (number의 주소)
1008         other       100

 

여기서 ptr은 number의 주소인 1000을 저장하고 있다.

 

포인터 선언과 초기화

기본 문법

#include <iostream>
using namespace std;

int main() {
    int number = 42;        // 일반 변수
    int* ptr = &number;     // 포인터 변수 (number의 주소를 저장)

    cout << "number의 값: " << number << endl;
    cout << "number의 주소: " << &number << endl;
    cout << "ptr이 저장하는 주소: " << ptr << endl;
    cout << "ptr이 가리키는 값: " << *ptr << endl;

    return 0;
}

중요한 연산자들

  • & (주소 연산자): "~의 주소를 알려줘"
  • (역참조 연산자): "이 주소에 있는 값을 알려줘"

초보자 실수 방지 팁

int* ptr1, ptr2;  // 주의! ptr1만 포인터, ptr2는 일반 int
int *ptr3, *ptr4; // 둘 다 포인터 (권장하지 않음)
int* ptr5;        // 명확한 포인터 선언 (권장)
int* ptr6;

 

 

포인터 연산의 특별한 규칙

포인터 산술의 핵심 개념

일반적인 숫자 계산과 달리, 포인터 연산은 데이터 타입의 크기를 고려한다.

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* ptr = arr;  // 배열의 첫 번째 원소를 가리킴

    cout << "ptr이 가리키는 값: " << *ptr << endl;        // 10
    cout << "ptr의 주소: " << ptr << endl;

    // 포인터 증가 - 중요한 개념!
    ptr++;  // 다음 int 위치로 이동 (4바이트 증가)
    cout << "ptr++ 후 값: " << *ptr << endl;              // 20
    cout << "ptr++ 후 주소: " << ptr << endl;

    // 포인터 + 숫자
    ptr = arr;  // 다시 처음으로
    cout << "ptr + 2의 값: " << *(ptr + 2) << endl;      // 30

    return 0;
}

왜 이렇게 동작할까?

  • int는 4바이트를 차지함.
  • ptr + 1은 메모리 주소를 4바이트 증가.
  • char*라면 1바이트, double*라면 8바이트씩 증가.

 

실습: 값 교환 함수

#include <iostream>
using namespace std;

// 잘못된 방법 - 값 복사
void swapWrong(int a, int b) {
    cout << "swapWrong 함수 내부에서:" << endl;
    cout << "교환 전: a=" << a << ", b=" << b << endl;

    int temp = a;
    a = b;
    b = temp;

    cout << "교환 후: a=" << a << ", b=" << b << endl;
    // 하지만 원본 변수는 바뀌지 않음!
}

// 올바른 방법 - 포인터 사용
void swapCorrect(int* a, int* b) {
    cout << "swapCorrect 함수 내부에서:" << endl;
    cout << "교환 전: *a=" << *a << ", *b=" << *b << endl;

    int temp = *a;
    *a = *b;
    *b = temp;

    cout << "교환 후: *a=" << *a << ", *b=" << *b << endl;
    // 원본 변수가 실제로 바뀜!
}

int main() {
    int x = 10, y = 20;

    cout << "=== 원본 값 ===" << endl;
    cout << "x = " << x << ", y = " << y << endl;

    cout << "\\n=== 잘못된 교환 시도 ===" << endl;
    swapWrong(x, y);
    cout << "함수 호출 후: x = " << x << ", y = " << y << endl;

    cout << "\\n=== 올바른 교환 ===" << endl;
    swapCorrect(&x, &y);
    cout << "함수 호출 후: x = " << x << ", y = " << y << endl;

    return 0;
}

왜 이런 차이가 생길까?

  • 값 전달: 변수의 복사본을 함수에 전달 → 원본 변경 불가
  • 포인터 전달: 변수의 주소를 함수에 전달 → 원본 변경 가능

 

포인터 디버깅 팁

포인터 값 확인하기

#include <iostream>
using namespace std;

int main() {
    int number = 100;
    int* ptr = &number;

    cout << "=== 포인터 정보 확인 ===" << endl;
    cout << "number 변수의 값: " << number << endl;
    cout << "number 변수의 주소: " << &number << endl;
    cout << "ptr 변수가 저장하는 주소: " << ptr << endl;
    cout << "ptr 변수 자체의 주소: " << &ptr << endl;
    cout << "ptr이 가리키는 값: " << *ptr << endl;

    // 포인터 유효성 확인
    if (ptr != nullptr) {
        cout << "포인터가 유효한 주소를 가리키고 있습니다." << endl;
    }

    return 0;
}

초보자 주의사항

1. 초기화되지 않은 포인터

int* ptr;  // 위험! 쓰레기 값을 가지고 있음
// *ptr = 10;  // 런타임 에러 발생 가능

int* safePtr = nullptr;  // 안전한 초기화

2. 잘못된 포인터 연산

int number = 42;
int* ptr = &number;

// 올바른 사용
cout << *ptr << endl;  // 42 출력

// 잘못된 사용
// cout << ptr + 100 << endl;  // 의미 없는 주소

3. 포인터와 배열의 혼동

int arr[3] = {1, 2, 3};
int* ptr = arr;  // 올바름: 배열 이름은 첫 번째 원소의 주소

// arr++; // 오류! 배열 이름은 상수
ptr++;    // 올바름: 포인터는 변경 가능

정리

핵심 개념 요약

  1. 포인터: 메모리 주소를 저장하는 변수
  2. & 연산자: 변수의 주소를 구함
  3. 연산자: 주소에 있는 값을 가져옴
  4. 포인터 연산: 데이터 타입 크기만큼 증가/감소
  5. 함수 인자: 포인터로 전달하면 원본 수정 가능

 

반응형

댓글