포인터 — 메모리의 이해
C언어의 가장 큰 고비이자 핵심 무기입니다. 값을 통째로 복사하는 대신, "데이터가 있는 집 주소"를 알려주어 원본을 직접 조작하고 메모리를 효율적으로 다루는 방법을 배웁니다.
학습 목표
& (주소 연산자)와 * (역참조 연산자)의 차이를 정확히 구분한다.NULL 포인터의 개념을 알고 안전하게 코딩하는 습관을 기른다.포인터란 무엇인가?
포인터를 한 문장으로 정의하면: "다른 변수의 메모리 주소를 저장하는 변수"입니다.
우리가 int number = 100; 이라고 변수를 만들면, 컴퓨터는 메모리 어딘가에 공간을 만들고 거기에 100을 넣습니다. 이 공간은 현실 세계의 집 주소처럼 고유한 메모리 주소를 가지고 있습니다.
포인터 변수 ptr은 값이 아니라 number의 집 주소(0x1000)를 저장하여 number를 가리키고(Point) 있습니다.
메모리와 주소 연산자 (&)
변수가 위치한 주소를 알고 싶을 때는 변수명 앞에 & 기호를 붙입니다.
int number = 100; printf("값 : %d\n", number); // %p는 주소(Pointer)를 16진수로 출력하는 형식 지정자입니다. printf("주소 : %p\n", (void *)&number);
값 : 100 주소 : 0x7ffd12345678
포인터 변수 선언과 주소 저장
주소를 담으려면 평범한 int가 아니라 포인터형 변수가 필요합니다. 자료형 뒤나 변수명 앞에 *를 붙여서 만듭니다.
int number = 100; // 정수(int)가 들어있는 방의 주소를 담을 포인터(ptr) 선언 int *ptr; // number의 주소(&number)를 ptr에 저장한다! ptr = &number; // 이제 ptr은 number를 가리키고 있습니다.
역참조 연산자 (*)
포인터가 가진 가장 중요한 기능입니다. *ptr이라고 쓰면 "ptr이 가리키고 있는 주소로 직접 찾아가서, 그 안에 있는 값을 가져오거나 바꿔라" 라는 의미입니다.
&변수 : 주소를 얻는다.
ptr : 주소를 저장한다.
*ptr : 주소로 찾아가서 값을 다룬다.
int number = 100; int *ptr = &number; printf("변경 전 number: %d\n", number); // ptr이 가리키는 곳(number)에 가서 값을 200으로 바꿔버림! *ptr = 200; // number를 직접 바꾼 적은 없지만, 포인터가 바꿨기 때문에 200이 출력됨 printf("변경 후 number: %d\n", number);
함수와 포인터 (값 복사 문제 해결)
STEP 08에서 함수로 변수를 넘겨도 원본은 안 바뀐다고 했습니다 (값만 복사되니까).
하지만 원본 변수의 주소를 넘겨주면 함수 안에서도 원본을 직접 뜯어고칠 수 있습니다!
#include <stdio.h> // 포인터 변수로 주소를 넘겨받습니다. void swap(int *a, int *b) { int temp = *a; // a가 가리키는 원본 값을 임시 저장 *a = *b; // b가 가리키는 값을 a가 가리키는 곳에 복사 *b = temp; // 임시 저장된 값을 b가 가리키는 곳에 복사 } int main() { int x = 10; int y = 20; // 주소를 넘겨줍니다! swap(&x, &y); printf("x=%d, y=%d\n", x, y); // x=20, y=10 으로 진짜 교환됨! return 0; }
배열과 포인터의 밀접한 관계
C언어에서 배열의 이름은 배열의 첫 번째 원소가 있는 시작 주소와 완벽히 똑같이 동작합니다.
int number[5] = {10, 20, 30, 40, 50}; // 배열 이름(number)이 곧 주소이므로, & 기호 없이 포인터에 담을 수 있습니다. int *ptr = number; // 다음 두 코드는 완전히 똑같은 값을 가리킵니다. printf("%d\n", number[2]); // 배열 방식 (30) printf("%d\n", *(ptr + 2)); // 포인터 연산 방식 (30)
NULL 포인터와 주의사항
int *ptr; 까지만 선언하고 *ptr = 100; 을 실행하면 안 됩니다! ptr에 아무 주소나 쓰레기값으로 들어가 있는데 거길 찾아가서 값을 바꾸면 프로그램이 터지거나(Segmentation Fault) OS가 다운될 수 있습니다.
당장 포인터에 넣을 주소가 없다면 반드시 NULL(아무것도 가리키지 않음)로 초기화해야 안전합니다.
int *ptr = NULL; // 포인터를 사용하기 전에 주소가 세팅되었는지 확인하는 습관! if (ptr != NULL) { printf("%d", *ptr); }
STEP 09 최종 프로젝트
포인터를 이용한 다중 반환 성적 처리 프로그램
C언어 함수는 `return`으로 값을 1개밖에 못 돌려줍니다. 하지만 포인터를 사용하면 함수의 매개변수 주소를 통해 최댓값과 최솟값을 동시에(다중 반환 효과) main()에 넘겨줄 수 있습니다!
// 함수 안에서 원본 min과 max의 값을 동시에 수정합니다. void findMinMax(int score[], int size, int *min, int *max) { // 구현해 보세요! (*min, *max 활용) } int main() { int score[5] = {25, 73, 12, 98, 45}; int min, max; // min과 max의 주소를 함수에 던져줍니다. findMinMax(score, 5, &min, &max); printf("최솟값: %d\n최댓값: %d\n", min, max); return 0; }
STEP 09 완료 체크리스트
포인터는 Arduino 같은 아두이노/임베디드 제어나, 대용량 게임 데이터 처리 등 하드웨어를 직접 제어하고 메모리를 극한으로 쥐어짜는 C언어 최고의 무기입니다. 포인터를 이해했다면 이제 C언어의 절반을 정복한 셈입니다!