Krafton Jungle/2. Keywords

[WEEK05] 포인터와 포인터 연산

munsik22 2025. 4. 11. 22:43

이미 지난 주차에 포인터에 대해 정리한 글을 투고했지만, C에서 포인터 개념이 중요하게 사용되기 때문에 다시 한 번 다루게 되었다. 오늘은 C언어에서 정말 중요한 개념인 포인터pointer, 역참조dereferencing, 그리고 이중 포인터double pointer에 대해 정리해봤다. 이 개념들은 자료구조를 구현할 때 거의 필수로 사용되기 때문에 제대로 이해하고 넘어가는 게 중요하다.

🔹 포인터

포인터는 간단히 말하면 "주소를 저장하는 변수"다. 일반 변수는 데이터를 저장하지만, 포인터는 그 데이터가 저장된 메모리 주소를 저장한다.

int a = 10;
int *p = &a;  // p는 a의 주소를 저장

여기서 *p는 포인터이고, &a는 변수 a의 주소다. 즉, 포인터 p는 a를 가리키고 있다고 볼 수 있다.

🔸 역참조

포인터가 주소를 저장하고 있다면, 그 주소에 있는 실제 값에 접근하려면 어떻게 해야 할까? 그때 사용하는 것이 역참조 연산자 * 다.

printf("%d\n", *p);  // a의 값인 10이 출력됨

*p는 p가 가리키는 주소의 실제 값을 의미한다.

정리하자면, *는 선언할 때는 포인터임을 나타내고사용할 때는 역참조 연산자로 쓰인다. 문맥에 따라 다르게 쓰이므로 헷갈리지 않도록 주의해야 한다!


🔹 이중 포인터

이중 포인터는 말 그대로 "포인터를 가리키는 포인터"다. 즉, 포인터의 주소를 저장하는 포인터라고 생각하면 된다.

int a = 10;
int *p = &a;
int **pp = &p;  // pp는 p의 주소를 저장

여기서 구조는 이렇게 된다:

  • a: 실제 값 (10)
  • p: a의 주소를 가리킴
  • pp: p의 주소를 가리킴

그래서 *pp는 p이고, **pp는 a다.

printf("%d\n", **pp);  // 결과: 10

🔸 이중 포인터를 언제 사용할까?

1. 함수에서 포인터를 수정하고 싶을 때

void setPointer(int **pp) {
    *pp = (int *)malloc(sizeof(int));
    **pp = 100;
}

이중 포인터를 통해 함수 바깥의 포인터 변수 자체를 바꿀 수 있다.

2. 이차원 배열 구현

int **matrix;
matrix = (int **)malloc(sizeof(int*) * rows);
for (int i = 0; i < rows; i++)
    matrix[i] = (int *)malloc(sizeof(int) * cols);

3. 포인터 배열 (예: char **argv)

  • main 함수의 매개변수 char **argv도 사실 이중 포인터!

💻 이중 포인터 예제 코드

1. 함수에서 포인터 값을 변경하기

C언어에서 함수는 값에 의한 호출call by value이기 때문에, 포인터 변수 자체를 변경하려면 이중 포인터가 필요하다.

#include <stdio.h>
#include <stdlib.h>

void allocateAndSet(int **ptr) {
    *ptr = (int *)malloc(sizeof(int));  // 포인터에 동적 메모리 할당
    if (*ptr != NULL) {
        **ptr = 42;  // 역참조해서 값 설정
    }
}

int main() {
    int *p = NULL;

    allocateAndSet(&p);  // 이중 포인터로 전달

    if (p != NULL) {
        printf("할당된 값: %d\n", *p);  // 결과: 42
        free(p);  // 동적 메모리 해제
    }

    return 0;
}

 

  • allocateAndSet() 함수는 포인터의 포인터 int **ptr을 통해 메모리 할당 + 값 설정을 한다.
  • 이중 포인터가 아니면 함수에서 p 자체를 바꿀 수 없다.

2. 이중 포인터로 2차원 배열 동적 할당

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 4;
    int **matrix;

    // 행 포인터 배열 동적 할당
    matrix = (int **)malloc(sizeof(int*) * rows);

    // 각 행마다 열 배열 동적 할당
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(sizeof(int) * cols);
    }

    // 배열 값 초기화 및 출력
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
            printf("%2d ", matrix[i][j]);
        }
        printf("\n");
    }

    // 메모리 해제
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}
  • int **matrix는 2차원 배열처럼 사용할 수 있다.
  • 행마다 별도로 메모리 할당한 후, 2차원 배열처럼 접근 가능 (matrix[i][j])

💡 아래 코드 쌍들은 서로 동일한 동작을 실행하는 코드이다.

matrix[i][j] = ++idx;
*((*(matrix + i)) + j) = ++idx;

printf("%d\n", matrix[2][3]);
printf("%d\n", *(*(matrix + 2)) + 3);

3. 포인터를 이용한 문자열 처리

문자열은 사실 문자 배열char array이고, 문자열의 시작 주소를 가리키는 char 포인터를 통해 다룰 수 있다.

#include <stdio.h>

void printString(char *str) {
    while (*str != '\0') {
        putchar(*str);  // 현재 문자 출력
        str++;          // 다음 문자로 이동
    }
    putchar('\n');
}

int main() {
    char message[] = "Hello, pointer!";
    printString(message);  // 문자열의 첫 주소를 전달
    return 0;
}
  • char *str은 문자열의 시작 주소를 받는다.
  • 포인터를 증가시키며 문자열을 순회한다. (str++)
  • 배열 이름(message)은 포인터처럼 첫 번째 문자의 주소를 의미함.

4. 함수 포인터 사용하기

함수 포인터는 함수의 주소를 저장하는 포인터다. 상황에 따라 함수를 매개변수로 넘기거나, 조건에 따라 함수 호출할 때 유용하게 쓰인다.

#include <stdio.h>

// 두 개의 함수 정의
int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// 함수 포인터를 매개변수로 받는 함수
void calculate(int x, int y, int (*func)(int, int)) {
    printf("결과: %d\n", func(x, y));
}

int main() {
    calculate(3, 4, add);      // 결과: 7
    calculate(3, 4, multiply); // 결과: 12

    return 0;
}

(Tip) 함수 포인터 배열도 가능!

int (*operations[])(int, int) = { add, multiply };
operations[0](5, 6);  // add
operations[1](5, 6);  // multiply

함수 선택 메뉴를 만들 때 유용하게 사용될 수 있다.


🔹 포인터 연산

포인터 연산Pointer Arithmetic이란 포인터끼리 덧셈, 뺄셈, 비교 등을 수행하는 것이다. C언어에서는 포인터가 가리키는 자료형의 크기를 기준으로 연산이 일어난다. 예를 들어 int는 보통 4바이트니까, p + 1은 다음 int 위치(4바이트 뒤)를 가리킨다.

🔸 기본 예제: 배열과 포인터

#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("%d\n", *p);     // 10
    printf("%d\n", *(p+1)); // 20
    printf("%d\n", *(p+2)); // 30

    return 0;
}

 

  • p는 arr[0]을 가리킴
  • p + 1은 arr[1]을 가리킴
  • 포인터 연산은 배열 인덱스처럼 동작함

🔹 사용할 수 있는 연산들

연산 의미
p + n 포인터를 n개의 요소만큼 앞으로 이동
p - n 포인터를 n개의 요소만큼 뒤로 이동
p1 - p2 두 포인터 사이의 거리(요소 개수 차이)
p++, p-- 포인터를 다음/이전 요소로 이동
p == q 두 포인터가 같은 주소를 가리키는지 비교

🔸 예제: 포인터 이동

#include <stdio.h>

int main() {
    char str[] = "Hello";
    char *p = str;

    while (*p != '\0') {
        printf("%c ", *p);
        p++;  // 다음 문자로 이동
    }
    return 0;
}
// 출력 : H e l l o

🔸 예제: 포인터 간 뺄셈

int arr[5] = {1, 2, 3, 4, 5};
int *start = &arr[0];
int *end = &arr[4];

int distance = end - start;  // 결과: 4
  • 같은 배열 내 포인터 간 뺄셈은 몇 개 떨어져 있는지를 알 수 있다.

🔸 예제: 배열 순회

#include <stdio.h>

int main() {
    int nums[] = {10, 20, 30, 40, 50};
    int *p = nums;

    for (int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));  // 포인터 + i
    }

    return 0;
}
// 출력 : 10 20 30 40 50

🔸 예제: 포인터로 중간 요소 접근 및 수정

#include <stdio.h>

int main() {
    int arr[] = {100, 200, 300, 400, 500};
    int *p = arr;

    *(p + 2) = 999;  // 세 번째 요소(300 → 999)

    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}
// 출력 : 100 200 999 400 500

🚫 주의할 점

  • 포인터 연산은 같은 배열 내에서만 안전하게 해야 한다.
  • 배열 범위를 벗어나면 정의되지 않은 동작undefined behavior이 발생할 수 있다.
  • 포인터끼리 덧셈은 불가능하다. (p + q ← ❌)

정리

내용 설명
포인터 + 정수 메모리 상 다음 요소로 이동
포인터 - 포인터 두 포인터 간 거리(요소 개수)
포인터 증가/감소 배열이나 문자열 순회에 유용
주의사항 배열 범위 밖 접근 금지, 다른 포인터끼리 덧셈 불가

[참고자료]

 

COS Pro 2급 C 언어: 44.1 포인터 연산으로 메모리 주소 조작하기

포인터 연산은 포인터 변수에 +, - 연산자를 사용하여 값을 더하거나 뺍니다. 또는, ++, -- 연산자를 사용하여 값을 증가, 감소시킵니다. 단, *, / 연산자와 실수 값은 사용할 수 없습니다. 포인터 +

dojang.io

 

[C언어] 포인터를 사용하는 이유

포인터 없이 두 변수의 값을 바꾸는 함수는 불가능할까? 우선 swap 함수에서 main 함수의 a, b를 이름으로 직접 사용하는 방법을 생각해 보겠습니다. 함수 안에 선언된 변수명은 사용 범위가 함수

hongong.hanbit.co.kr

 

C언어 문법 - 포인터(Pointer)

Python과 결이 매우 다른 C언어.. 그냥저냥 할 만했지만, 이제는 어렵다.바로 '포인터'의 개념이 처음 등장했기 때문.그래서 Pointer에 대해 공부한 내용을 정리하고자 글을 썼다. 프로그래밍 언어에

velog.io

 

09-09. 포인터와 함수

[TOC] ## 예제 1: 값에 의한 함수 호출 (Call-by-value) ```{.c} #include int func(int i); int main(void) { …

wikidocs.net

 

왜 C언어 포인터는 이해하기 어려울까? - 인프런 | 스토리

교수님! 진도가 너무 빠릅니다 😭 C언어 입문의 최종보스, 들어는 봤나 포인터! C언어 배워보신 분 손! 프로그래밍에 관심이 있다면 한 번쯤은 ‘C언어에서 포인터가 그렇게 어렵다더라’ 하는

www.inflearn.com

 

[크래프톤 정글 ] 포인터

C 언어에서 포인터는 변수의 메모리 주소를 저장하는 변수이다.즉, 포인터는 다른 변수나 메모리 상의 데이터의 위치를 가리키는 역할을 한다. 이로 인해 직접적으로 메모리 주소를 다룰 수 있

developsvai5096.tistory.com