C언어

[C 언어] 포인터(Pointer)의 이해

Teodore 2023. 2. 25. 17:18
728x90

Intro

많은 사람들이 C 언어를 어려워 하는 가장 큰 이유가 포인터이다. 

포인터는 항상 메모리와 연관해서 설명되고 간접 참조하기 때문에 직관적으로 이해되지 않는 경우가 있기 때문이다. 

그런데 실무에 들어가서 코딩하다보면 정작 포인터를 많이 안쓰게 되는 경우가 있다. 

 

만드는 프로그램이 단순하거나 포인터의 동작에 대해 자신이 없는 경우 전역 변수로 처리하기 때문이다. 

 

포인터란?

직접 개발을 하면서 깨달은 포인터의 의미를 쉽게 설명해 보려 한다. 

포인터는 말 그대로 가르키는 것이다. 복잡하게 생각할 필요가 없다는 뜻이다. 

누군가에게 길을 물어본다고 하면 아래 그림처럼 길을 알려주는 사람은 손가락으로 가르킬 것이다. 

출처 - 픽사베이

그럼 물어본 사람은 그 손가락을 따라 시선을 이동하게 되는데 바로 이것이 포인터이다. 

무슨 뜬구름 잡는 소리냐고?

다음 코드를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int func_value(int val){
    return val + 1;
}
 
int func_pointer(int* val){
    *val = *val + 1;
    return 0;
}
 
int main(void){
    int a = 1;
    a = func_value(a);
    printf("a is %d\n", a);
    func_pointer(&a);
    printf("a is %d\n", a);
    return 0;
}
cs

func_value는 a값을 전달받아서(즉, a값을 다른 주머니에 복사해서 전달) 1을 더한 후 리턴하게 되고, 리턴된 값을 다시 a에 넣어 업데이트 하도록 하였다. 

func_pointer는 a가 있는 곳을 알려준 뒤 함수가 직접 a에게 접근하여 값을 업데이트 하도록 하였다. 

 

어떤 코드가 더 좋아 보이는지 물어본다면 아마 확신이 없을 수 있다. 

그러나 함수의 이름과 기능이 명확해 질수록 포인터를 사용했을 때 실수할 수 있는 가능성을 줄여줄 수 있다. 

 

때때로는 단순하게 생각하는 것이 가장 이해하기 쉬울 수 있다는 것을 기억하자. 

 

기능 확장

1
2
3
4
5
6
7
8
9
10
int(*func(void))[4]
{
    static int a[3][4= {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    return a;
}
 
void main(void)
{
    printf("%d\n", func()[1][2]);
}
cs

사실 포인터는 1차원 배열에 사용하는 경우도 많지만 제대로 활용하려면 2차원 이상의 배열, 구조체, 함수 포인터를 사용해야 한다. 

위의 코드는 2차원 배열을 func에서 생성하여(static으로 선언하였으므로 함수가 종료되어도 사라지지 않는다.) 반환해주는 함수이다. 

프로그래밍에서 중요한 모듈화를 할때 걸림돌이 되는 것 중 하나가 바로 변수를 분리하는 것인데, 위의 코드를 활용하면 변수의 범위를 제한할 수 있기 때문에 독립적인 모듈 설계가 가능해진다. main등 호출하는 콜러쪽에서는 변수의 값만 가져다 쓰고 변수에 대한 변경을 하지 않기 때문에 상황에 맞는 값을 생성하는 알고리즘은 모두 함수에서 해결하도록 기능을 부여할 수 있다. 

 

위 코드가 잘 이해되지 않는 경력이 높은 개발자도 분명히 있을 것이다. 왜냐하면 잘못알려진 코딩 지식 중에 2차원 배열은  int**로 return type을 선언하여 int** func(void)로 사용하는 경우가 있기 때문이다. 

그러나 이렇게 설계해버리면 1차원 배열과 크게 다를 것이 없게 되버리고 정확한 인덱싱 및 사이즈를 확인할 수 없게 되버린다. sizeof 연산자와 연계하여 변수의 크기를 재는데 오류가 발생한다는 뜻이다. 

 

포인터는 처음 배울때부터 정확한 개념을 가지고 타입의 원형을 포인터 형식으로 적절히 변경할 수 있을 때 파워풀해진다. 

 

오늘은 여기까지

 

728x90