본문 바로가기
카테고리 없음

C언어 - 포인터 완전 정리

by kik328288 2026. 5. 3.

C언어 포인터 완전 정리, 실기 예제 포함

C언어를 배우는 사람이라면 누구나 한 번쯤은 포인터 앞에서 좌절을 겪는다. 메모리 주소라는 개념 자체가 추상적이고, 별표(*)와 앰퍼샌드(&)가 같은 코드 안에서 서로 다른 의미로 쓰이며, 한 줄을 잘못 작성하면 프로그램이 통째로 죽어버리는 경험이 흔하기 때문이다. 그러나 포인터는 C언어의 진정한 힘이자, 배열·문자열·동적 메모리·자료구조·운영체제 같은 모든 후속 영역의 토대이다. 정보처리기사 실기 시험에서도 포인터 기반 코드의 출력 결과를 묻는 문제가 매회 출제될 만큼 비중이 높다. 본 글은 포인터의 기본 개념부터 배열과의 관계, 그리고 실기 시험에 자주 등장하는 빈출 예제까지 한 번에 정리한다.

 

포인터의 기본 개념과 메모리 동작

포인터(Pointer)란 다른 변수의 메모리 주소를 저장하는 특수한 변수이다. 일반 변수가 값을 직접 담는다면, 포인터는 값이 저장된 위치, 즉 주소값을 담는다. 컴퓨터 메모리는 0부터 시작해 일정한 주소가 부여된 거대한 사물함이라고 비유할 수 있는데, 포인터는 그 사물함 번호를 적어둔 메모이며, 그 번호를 따라가면 실제 데이터에 도달할 수 있는 셈이다. 이 단순한 개념 위에서 C언어의 거의 모든 고급 기능이 구축된다.

포인터를 선언할 때는 자료형 뒤에 별표(*)를 붙여 "자료형 *포인터명;" 형식으로 작성한다. 변수의 주소를 얻을 때는 주소 연산자 앰퍼샌드(&)를 사용하며, 포인터가 가리키는 주소의 실제 값에 접근할 때는 역참조 연산자 별표(*)를 사용한다. 같은 별표가 선언과 역참조에서 다른 의미로 쓰인다는 점이 입문자가 가장 헷갈려하는 지점이다.

int a = 10;        // 일반 정수 변수
int *p = &a;       // 포인터 p에 a의 주소 저장

printf("%d\n", a);   // 10 (변수 a의 값)
printf("%p\n", &a);  // a의 주소 (예: 0x7fff...)
printf("%p\n", p);   // p에 저장된 주소 (a의 주소와 동일)
printf("%d\n", *p);  // 10 (p가 가리키는 곳의 값 = a의 값)

*p = 20;             // p가 가리키는 메모리에 20 저장 → a가 20으로 변경
printf("%d\n", a);   // 20

위 예제에서 핵심은 마지막 두 줄이다. 포인터 p를 통해 a의 메모리 주소에 접근해 값을 바꾸면, a 자신을 수정한 것과 동일한 효과가 발생한다. 이것이 바로 #28 글에서 다룬 Call by Reference의 원리이며, 함수 외부의 변수를 함수 내부에서 변경할 수 있게 만드는 메커니즘이다. 포인터는 자료형이 정해져 있어야 한다는 점도 중요한데, int 포인터는 4바이트씩, char 포인터는 1바이트씩 메모리를 다루기 때문에 잘못된 자료형으로 캐스팅하면 의도치 않은 메모리 침범이 발생할 수 있다.

포인터와 배열·문자열의 관계

C언어에서 배열의 이름은 사실상 첫 번째 원소의 주소이다. 즉 "int arr[5]"라고 선언하면 arr 자체가 &arr[0]과 동일한 값을 가지며, 따라서 배열은 포인터처럼 다룰 수 있다. 이 관계가 명확히 이해되면 배열의 모든 원소를 포인터 연산으로 순회할 수 있게 되고, 함수에 배열을 넘기는 작동 원리도 자연스럽게 이해된다.

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;       // arr은 &arr[0]과 같음

printf("%d\n", arr[2]);    // 30
printf("%d\n", *(p + 2));  // 30 (포인터 연산으로 동일하게 접근)
printf("%d\n", *(arr + 2));// 30 (배열 이름을 포인터처럼 사용)
printf("%d\n", p[2]);      // 30 (포인터를 배열처럼 사용)

위 네 줄은 모두 같은 값 30을 출력한다. arr[2]와 *(p + 2)와 *(arr + 2)와 p[2]가 본질적으로 같은 의미라는 사실이 C언어 포인터의 핵심 통찰이다. 포인터 연산에서 p + n은 단순한 +n이 아니라 "p가 가리키는 자료형의 크기 × n만큼 주소를 이동"하는 것이라는 점도 기억해야 한다. int가 4바이트이므로 p + 2는 실제로는 8바이트 떨어진 주소를 가리킨다.

문자열은 char 배열로 표현되며, 마지막에 NULL 문자(\0)가 붙어 종료된다. char 포인터로 문자열 리터럴을 가리키게 하면 매우 편리하게 다룰 수 있다.

char *s = "HELLO";

printf("%s\n", s);      // HELLO  (문자열 전체 출력)
printf("%c\n", *s);     // H      (첫 문자)
printf("%c\n", *(s+1)); // E      (두 번째 문자)
printf("%s\n", s+2);    // LLO    (세 번째 문자부터 끝까지)

이 패턴은 정보처리기사 실기에서 매회 출제되는 가장 핵심적인 형태이며, 다음 섹션에서 더 자세히 다룬다. 이중 포인터(double pointer)도 같은 원리의 확장이다. "int **pp"는 "int 포인터의 주소를 저장하는 포인터"이며, 동적 2차원 배열이나 함수에서 포인터 자체를 변경하고 싶을 때 사용된다. *pp는 중간 포인터를 의미하고, **pp는 최종 값에 도달한다는 점만 기억하면 그 이상의 복잡한 다중 포인터도 같은 패턴으로 풀어낼 수 있다.

정보처리기사 실기 빈출 포인터 예제

정보처리기사 실기의 프로그래밍 영역에서 포인터는 거의 매회 출제된다. 출제 패턴은 거의 정형화되어 있어, 자주 나오는 다섯 가지 유형만 정확히 익혀두면 시험에서 안정적인 점수를 얻을 수 있다. 각 유형을 코드와 함께 살펴보자.

유형 1: 문자열 포인터 출력 결과 묻기. 가장 단골 출제 형식으로, char 포인터에 문자열을 할당한 뒤 다양한 출력 형식을 묻는다.

char *p = "KOREA";

printf("%s\n", p);       // KOREA
printf("%s\n", p + 3);   // EA
printf("%c\n", *p);      // K
printf("%c\n", *(p+3));  // E
printf("%c\n", *p + 2);  // M

마지막 줄이 함정이다. *p + 2는 *(p+2)가 아니라 "*p의 값에 2를 더한 결과"이다. *p는 문자 'K'(ASCII 75)이고, 75 + 2 = 77이 되어 ASCII 77인 'M'이 출력된다. 연산자 우선순위에 따라 역참조(*)가 덧셈(+)보다 먼저 평가된다는 점을 정확히 기억해야 한다.

유형 2: 포인터 연산으로 변수 값 변경하기.

int a = 10, b = 20;
int *p = &a;
*p = *p + 5;   // a = 15
p = &b;        // 이제 p는 b를 가리킴
*p = *p - 5;   // b = 15
printf("%d %d\n", a, b);  // 15 15

포인터가 가리키는 대상이 중간에 바뀌면, 그 이후의 *p는 새로운 변수에 영향을 미친다는 점을 묻는 패턴이다.

유형 3: 배열과 포인터 산술의 결합.

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr + 2;     // arr[2]의 주소

printf("%d\n", *p);       // 3
printf("%d\n", *(p+1));   // 4
printf("%d\n", *(p-1));   // 2
printf("%d\n", p[2]);     // 5

포인터는 양방향으로 이동할 수 있으며, p[n]은 *(p+n)과 동일하다는 사실을 묻는다.

유형 4: 이중 포인터.

int a = 100;
int *p = &a;
int **pp = &p;

printf("%d\n", a);     // 100
printf("%d\n", *p);    // 100
printf("%d\n", **pp);  // 100
**pp = 200;
printf("%d\n", a);     // 200

**pp = 200 한 줄이 결국 a를 200으로 바꾸는 흐름을 추적할 수 있어야 한다. pp → p → a라는 두 단계의 간접 참조를 정확히 따라가는 것이 핵심이다.

유형 5: 구조체 포인터(-> 연산자).

typedef struct {
    int num;
    char *name;
} Student;

Student s = {1, "KIM"};
Student *ps = &s;

printf("%d\n", ps->num);    // 1
printf("%s\n", ps->name);   // KIM
printf("%d\n", (*ps).num);  // 1 (-> 와 동일)

ps->num은 (*ps).num의 축약형이며, 두 표현은 완전히 동일하다는 사실이 시험에 자주 출제된다. 이 다섯 가지 패턴을 손으로 직접 작성해 출력 결과를 추적하는 연습을 반복하면, 정보처리기사 실기의 포인터 영역은 완전히 안정적인 점수원이 된다. 결국 포인터는 외우는 개념이 아니라 메모리의 그림을 머릿속에 그려가며 흐름을 따라가는 사고의 도구이며, 그 그림을 정확히 그릴 줄 알게 되면 C언어의 거의 모든 어려운 부분이 한꺼번에 풀린다.


메타 디스크립션: C언어 포인터의 기본 개념과 메모리 동작, 배열·문자열과의 관계, 그리고 정보처리기사 실기 시험에 자주 출제되는 다섯 가지 빈출 포인터 예제(문자열 포인터·포인터 연산·이중 포인터·구조체 포인터)를 코드와 함께 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름