블로그로 돌아가기

[컴퓨터구조] 포인터

3분 소요
[컴퓨터구조] 포인터

포인터란 무엇인가? 🤔

포인터는 간단하게 메모리 주소를 담는 변수​​라 생각하면 된다.

우리가

int a = 10;

처럼 변수를 선언하면, 컴퓨터 시스템은 메모리의 어딘가에 4바이트(int의 크기) 공간을 확보하고

그곳에 10이라는 값을 저장한다.

이렇게 메모리 상에 확보한 공간은 메모리 주소를 통해 찾아갈 수 있고, C언어에서 이 주소를 직접 다룰 수 있게 해주는 것이 바로 포인터인 것이다.

쉽게 생각하면

  • 일반 변수​​: '사과'가 들어있는 상자 🍎

  • 메모리 주소​​: 그 상자가 놓인 '창고 A-3번 구역'이라는 위치 정보 ℹ️

  • 포인터 변수​​: '창고 A-3번 구역'이라고 적힌 쪽지 📝

쪽지(포인터)가 있으면 상자(변수)를 직접 들고 다니지 않아도 언제든 그 상자를 찾아갈 수 있을것이고,

이게 바로 포인터의 핵심 역할이다.

포인터 선언 및 초기화

포인터를 사용하려면 포인터를 타입으로 선언해야 한다.

int a = 10;
int *p; // 포인터 변수 p 선언 (int형 포인터)

// 이후 p에 변수 a의 메모리 주소를 저장하게 된다.

* 앞의 데이터 유형(여기서 int)은 p가 어떤 유형의 값을 가리킬지 알려준다.

C에서는 포인터 유형이 포인터 유형과 일치해야 한다.

따라서 int*int만 가리키고 char*char를 가리킬 수 있다.

(한 가지 특별한 예외는 void*로, 어떤 데이터 유형이든 가리킬 수 있지만, 지금은 생략.)

포인터 초기화

포인터를 사용하기 전에 유효한 대상을 가리켜야 한다.

포인터에 기존 변수의 주소를 할당하여 포인터를 초기화할 수 있고, 여기서 주소 연산자 &이 나온다.

&연산자와 *연산자

포인터를 제대로 사용하려면 이 두 연산자와 친해져야 한다.

헷갈리기 쉬우니 하나씩 살펴보자.

1. &: 주소 연산자(Adress-of Operator)

&는 변수 앞에 붙어서 그 변수의 메모리 시작 주소를 반환​하는 역할을 한다.

즉, &a라고 쓰면 “a의 메모리 주소”를 뜻하고 이를 포인터 p에 저장할 수 있다.

int a = 10; 
int *p; // p는 int형 변수를 가리킬 포인터 변수!

p = &a; // p에 변수 a의 메모리 주소값 대입

printf("%p\n", p); // a의 메모리 주소를 16진수로 출력

이제 포인터 p는 변수 a“가리키게”​ 됐다.

p의 값을 출력해보면 a의 값인 10이 아니라 0x16fdfea88같은 16진수 메모리 주소값이 출력된다!


⚠️ 주의!

&연산자는 실제 메모리 공간이 있는 대상에만 쓸 수 있다.

&10이나 &(a+1)같은 표현은 불가능하다.

상수는 메모리에 저장된 변수가 아니기 때문.

오직 ​선언된 변수처럼 실체가 있는 녀석의 주소만 가져올 수 있다!


2. *: 역참조 연산자(Dereference Operator)

*기호는 C에서 두 가지 의미로 쓰여서 헷갈리기 쉽다.

  1. 선언문에서:

    int *p; 처럼 변수가 포인터임을 나타낼 때.

  2. 일반 표현식에서:

    *p처럼 포인터가 가리키는 주소에 저장된 실제 값에 접근할 때.

    ⇒ 이것을 역참조​라고 부른다.

즉,

*p는 “p가 가리키는 곳에 있는 값”을 의미한다.

int a = 10; 
int *p;

p = &a;

printf("a의 값: %d\n", a);   // 결과: 10
printf("*p의 값: %d\n", *p); // 결과: 10 (p가 가리키는 곳의 값이므로 a와 같다)

*p = 100; // p가 가리키는 곳(즉, a)에 100을 저장한다.
printf("a의 값 변경 후: %d\n", a); // 결과: 100

위 코드에서 pa를 가리키고 있으니, *pa와 완전히 똑같이 취급할 수 있다.

*p = 100;이라는 코드가 결국 a = 100;과 동일한 효과를 내는 걸 볼 수 있고,

이것이 바로 포인터를 통한 간접적인 메모리 조작이다.


🔥 가장 중요한 포인터 사용 규칙

int p;와 같이 포인터 변수를 선언만 하고 아직 아무 주소도 할당하지 않은 상태,

즉 “쓰레기 값”을 가진 상태에서 역참조(*)를 시도하면 절대 안된다.

위 코드에서 만약 p = &a;를 하지 않고 *p = 10;를 먼저 시도했다면?

p가 어디를 가리키는지도 모르는데 값을 넣으려 하니 시스템에서

Segmentation Fault(잘못된 메모리 접근 오류)를 내며 프로그램을 강제 종료 시킬 것이다.

그러니 포인터를 사용할 때는 반드시 p = &a;처럼 유효한 주소로 초기화​한 후에만 역참조를 해야한다.

혹은 아무것도 가리키지 않을 때는 p = NULL;로 명시해두는 습관을 들이면 좋다.

(NULL은 0번 주소를 가리키는 특수한 포인터 값이다.)