Phase 2 · C언어 핵심 · 활용

포인터 — 메모리의 이해

C언어의 가장 큰 고비이자 핵심 무기입니다. 값을 통째로 복사하는 대신, "데이터가 있는 집 주소"를 알려주어 원본을 직접 조작하고 메모리를 효율적으로 다루는 방법을 배웁니다.

예상 학습 시간 : 180분
난이도 : ★★★★★ 고급
사전 지식 : 변수, 배열, 함수
다음 단계 : 문자열
01

학습 목표

1
메모리와 주소(Address)의 개념을 이해한다.
2
& (주소 연산자)* (역참조 연산자)의 차이를 정확히 구분한다.
3
포인터 변수를 통해 원본 변수의 값을 원격으로 변경할 수 있다.
4
함수에 주소를 넘겨주어(Pass by Reference) 원본 데이터를 수정한다.
5
배열의 이름이 포인터처럼 쓰인다는 사실과 포인터 연산을 이해한다.
6
NULL 포인터의 개념을 알고 안전하게 코딩하는 습관을 기른다.
02

포인터란 무엇인가?

포인터를 한 문장으로 정의하면: "다른 변수의 메모리 주소를 저장하는 변수"입니다.

우리가 int number = 100; 이라고 변수를 만들면, 컴퓨터는 메모리 어딘가에 공간을 만들고 거기에 100을 넣습니다. 이 공간은 현실 세계의 집 주소처럼 고유한 메모리 주소를 가지고 있습니다.

ptr (포인터 변수)
0x1000
0x2050 (자신의 주소)
number (일반 변수)
100
0x1000 (자신의 주소)

포인터 변수 ptr은 값이 아니라 number의 집 주소(0x1000)를 저장하여 number를 가리키고(Point) 있습니다.

03

메모리와 주소 연산자 (&)

변수가 위치한 주소를 알고 싶을 때는 변수명 앞에 & 기호를 붙입니다.

⚙️ C
int number = 100;

printf("값 : %d\n", number);
// %p는 주소(Pointer)를 16진수로 출력하는 형식 지정자입니다.
printf("주소 : %p\n", (void *)&number);
값 : 100
주소 : 0x7ffd12345678
04

포인터 변수 선언과 주소 저장

주소를 담으려면 평범한 int가 아니라 포인터형 변수가 필요합니다. 자료형 뒤나 변수명 앞에 *를 붙여서 만듭니다.

⚙️ C
int number = 100;

// 정수(int)가 들어있는 방의 주소를 담을 포인터(ptr) 선언
int *ptr; 

// number의 주소(&number)를 ptr에 저장한다!
ptr = &number; 

// 이제 ptr은 number를 가리키고 있습니다.
05

역참조 연산자 (*)

포인터가 가진 가장 중요한 기능입니다. *ptr이라고 쓰면 "ptr이 가리키고 있는 주소로 직접 찾아가서, 그 안에 있는 값을 가져오거나 바꿔라" 라는 의미입니다.

💡 핵심 요약 (암기 필수!)

&변수 : 주소를 얻는다.
ptr : 주소를 저장한다.
*ptr : 주소로 찾아가서 을 다룬다.

🎯
Practice
포인터를 이용해 원격으로 값 바꾸기
⚙️ C
int number = 100;
int *ptr = &number;

printf("변경 전 number: %d\n", number);

// ptr이 가리키는 곳(number)에 가서 값을 200으로 바꿔버림!
*ptr = 200;

// number를 직접 바꾼 적은 없지만, 포인터가 바꿨기 때문에 200이 출력됨
printf("변경 후 number: %d\n", number);
06

함수와 포인터 (값 복사 문제 해결)

STEP 08에서 함수로 변수를 넘겨도 원본은 안 바뀐다고 했습니다 (값만 복사되니까).
하지만 원본 변수의 주소를 넘겨주면 함수 안에서도 원본을 직접 뜯어고칠 수 있습니다!

⚙️ C
#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;
}
07

배열과 포인터의 밀접한 관계

C언어에서 배열의 이름은 배열의 첫 번째 원소가 있는 시작 주소와 완벽히 똑같이 동작합니다.

⚙️ 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)
08

NULL 포인터와 주의사항

초기화하지 않은 포인터의 위험성

int *ptr; 까지만 선언하고 *ptr = 100; 을 실행하면 안 됩니다! ptr에 아무 주소나 쓰레기값으로 들어가 있는데 거길 찾아가서 값을 바꾸면 프로그램이 터지거나(Segmentation Fault) OS가 다운될 수 있습니다.

당장 포인터에 넣을 주소가 없다면 반드시 NULL(아무것도 가리키지 않음)로 초기화해야 안전합니다.

⚙️ C
int *ptr = NULL;

// 포인터를 사용하기 전에 주소가 세팅되었는지 확인하는 습관!
if (ptr != NULL) {
    printf("%d", *ptr);
}
09

STEP 09 최종 프로젝트

🚀 Mission

포인터를 이용한 다중 반환 성적 처리 프로그램

C언어 함수는 `return`으로 값을 1개밖에 못 돌려줍니다. 하지만 포인터를 사용하면 함수의 매개변수 주소를 통해 최댓값과 최솟값을 동시에(다중 반환 효과) main()에 넘겨줄 수 있습니다!

⚙️ C
// 함수 안에서 원본 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;
}
10

STEP 09 완료 체크리스트

메모리 주소의 개념과 주소 연산자(&)의 역할을 설명할 수 있다.
포인터 변수(*)를 선언하고 다른 변수의 주소를 저장할 수 있다.
역참조 연산자(*)를 이용해 포인터가 가리키는 원본 값을 읽거나 바꿀 수 있다.
Swap 함수에서 왜 값을 복사하면 안 되고 주소(&)를 넘겨야 하는지 이해한다.
배열 이름이 포인터처럼 동작하며, *(ptr + i)의 의미를 이해한다.
포인터 선언 시 NULL 초기화의 중요성과 위험성(댕글링)을 알고 있다.
C언어의 가장 큰 고비를 넘으셨습니다!

포인터는 Arduino 같은 아두이노/임베디드 제어나, 대용량 게임 데이터 처리 등 하드웨어를 직접 제어하고 메모리를 극한으로 쥐어짜는 C언어 최고의 무기입니다. 포인터를 이해했다면 이제 C언어의 절반을 정복한 셈입니다!

이전 : Step 08 Step 10 : 문자열