[컴퓨터구조] 포인터
![[컴퓨터구조] 포인터](https://firebasestorage.googleapis.com/v0/b/cruz-lab.firebasestorage.app/o/images%2Fheroes%2Fhero-1766481739560.webp?alt=media&token=5b9c0f8e-145e-4142-9958-a3f885e9c132)
포인터란 무엇인가? 🤔
포인터는 간단하게 메모리 주소를 담는 변수라 생각하면 된다.
우리가
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에서 두 가지 의미로 쓰여서 헷갈리기 쉽다.
-
선언문에서:
int *p;처럼 변수가 포인터임을 나타낼 때. -
일반 표현식에서:
*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
위 코드에서 p가 a를 가리키고 있으니, *p는 a와 완전히 똑같이 취급할 수 있다.
*p = 100;이라는 코드가 결국 a = 100;과 동일한 효과를 내는 걸 볼 수 있고,
이것이 바로 포인터를 통한 간접적인 메모리 조작이다.
🔥 가장 중요한 포인터 사용 규칙
int p;와 같이 포인터 변수를 선언만 하고 아직 아무 주소도 할당하지 않은 상태,
즉 “쓰레기 값”을 가진 상태에서 역참조(*)를 시도하면 절대 안된다.
위 코드에서 만약 p = &a;를 하지 않고 *p = 10;를 먼저 시도했다면?
p가 어디를 가리키는지도 모르는데 값을 넣으려 하니 시스템에서
Segmentation Fault(잘못된 메모리 접근 오류)를 내며 프로그램을 강제 종료 시킬 것이다.
그러니 포인터를 사용할 때는 반드시 p = &a;처럼 유효한 주소로 초기화한 후에만 역참조를 해야한다.
혹은 아무것도 가리키지 않을 때는 p = NULL;로 명시해두는 습관을 들이면 좋다.
(NULL은 0번 주소를 가리키는 특수한 포인터 값이다.)