콘텐츠로 건너뛰기

포인터의 재해석: 닻(Anchor)과 타이타닉

C언어의 포인터가 어렵게 느껴지는 이유는 &* 기호가 단순한 연산자가 아니라, 메모리를 항해하는 도구이기 때문입니다. 오늘은 이 두 기호를 ‘타이타닉 호를 찾는 모험’에 비유해 정리해 봅니다.

1. & (Ampersand): 닻 (Anchor)

키보드의 & 기호를 잘 보면, 배를 정박시킬 때 쓰는 닻(Anchor) 모양을 닮았습니다. 여기서 앵커는 배의 메인 앵커가 아닌 부표에 연결되어 있다고 생각해봅니다. 앵커는 우리가 메모리의 심해에서 어렵게 찾아낸 타이타닉과 연결되어 있습니다.

  • 닻을 내리는 이유: 배가 떠내려가지 않고 특정 위치(주소)에 머물게 하기 위해서입니다.
  • 프로그래밍에서의 의미: 변수 앞에 &를 붙이면, 그 앵커의 위치(메모리)를 알아냅니다.
int titanic = 100; // 보물(값)이 담긴 타이타닉 호
int *buoy = &titanic; // 내가 부표(포인터)를 투하해서 앵커에 연결해 놓을께!

윗 코드는 내가 타이타닉호를 발견했는데, 그 위치를 부표(부표 이름:buoy)를 뛰어서 알려줄께라는 의미입니다.

2. * (Asterisk): 타이타닉 (Content)

우리가 바다 한가운데에 부표를 띄운 이유는 무엇일까요? 부표 그 자체가 목적이 아닙니다. 바로 그 아래 가라앉아 있는 타이타닉(내용물)을 찾기 위해서입니다.

  • 의미: 부표(포인터 변수 앞)에 *를 붙이는 것은 “앵커 줄을 타고 내려가서, 그 끝에 있는 것(타이타닉)을 확인하라”는 명령입니다.
  • 용어: 이를 전문 용어로 ‘역참조(Dereference)’라고 합니다. 주소를 통해 실제 값에 도달한다는 뜻이죠.
printf("%d", *buoy); 
// 해석: 부표(buoy)가 떠 있는 곳 아래로 내려가라(*). 
// 무엇이 있는가? -> 타이타닉(100)이 있다.

3. 사고 실험: 이중 포인터를 직접 만들어보자 (실패기)

그냥 포인터는 쉽죠? 그런데 조금 어려운 개념이 있습니다. 바로 이중 포인터.

여기서는 이중 포인터(**)의 필요성을 이해하기 위해, 일부러 틀린 코드를 작성해 보았습니다. 첫 오류는 여기서 시작합니다.

“포인터도 결국 주소(숫자)니까, 그냥 숫자를 담는 변수에 넣어서 가리키면 되지 않을까?”

여기에는 마커(marker 또는 네임태그)라는 개념이 필요합니다. 즉, 배에 연결된 부표를 관리하기 위해서 배에 연결된 선에 표시하는 네임태그라는 것이 필요합니다.

이제 부표는 선미, 선창, 기관실 등등을 가리키고 있다고 가정해 봅니다. 물론, 부표의 네임태그를 바꾸거나 추가하는 경우도 있을 것이다.

#include <stdio.h>

int main() {
    int titanic = 100;    // 실제 값
    int *buoy = &titanic; // 부표 (타이타닉의 위치)

    // 부표의 위치(&buoy)도 결국 숫자 주소니까, 
    // 그냥 unsigned int 포인터에 담아서 가리켜보자.

    unsigned int *marker = &buoy; // <--- [에러 발생!]
    // 내 마커는 부표에 연결되어 있다.
    // 타이타닉으로 연결하려면 (**)를 넣으면 될 것 같다?
    
    // **marker = 200; // <--- [에러 발생!]
    // 마커를 보고 부표로 간 다음에 타이타닉호에 접근해라.
}

   [컴파일 에러 발생]
   10 |     unsigned int *marker = &buoy;

왜 실패했을까?

왜냐하면, unsigned int *marker라고 선언한 순간, 컴파일러는 marker가 가리키는 대상이 ‘단순한 숫자(Unsigned Int)’라고 생각합니다.

  1. *marker: buoy에 도착했습니다. (성공)
  2. **marker: 도착해서 보니, 컴파일러 눈에는 이것이 ‘그냥 숫자 덩어리’로 보입니다. 숫자를 타고 내려갈 수는 없으므로 에러가 납니다.

4. 해결: 족보를 확실히 하라 (int **)

부표를 가리키는 또 다른 표식(Marker)을 만들려면, “내가 가리키는 것은 단순한 숫자가 아니라, 또 다른 부표(포인터)다!”라고 명시해야 합니다.

int titanic = 100;
int *buoy = &titanic;    // Level 1: 타이타닉의 위치
int **marker = &buoy;    // Level 2: 마커는 부표의 위치를 가리킴. 부표와 연결된 선의 이름(네임태그)

이제 컴파일러는 marker를 보고 이렇게 해석합니다.

  1. *marker: 마커(네임 태그)를 따라가니 부표(buoy)가 있군. (이 안에는 주소가 들어있어!)
  2. **marker: 그 부표의 앵커 줄을 타고 한 번 더 내려가니 타이타닉(titanic)이 있군!

5. 요약

  • & (닻): 변수가 메모리 어디에 있는지 위치(주소)를 고정합니다.
  • * (타이타닉 확인): 앵커를 내린 진짜 이유인 내용물(값)을 확인하러 내려갑니다.
  • ** (이중 포인터): 타이타닉을 찾기 위해 부표를 띄웠고(*), 그 부표를 찾기 위해 또 다른 마커(네임 태그)를 띄운(**) 상태입니다.

포인터는 결국 “무엇을 찾기 위해 닻을 내렸는가?”를 꼬리에 꼬리를 물고 찾아가는 과정입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다