본문 바로가기
프로그래밍/C언어

C언어 공부하기 - 포인터를 알아보자

by 헬맷쓰다 2020. 10. 7.
반응형

C언어를 공부할 때 많은 사람들이 다른 언어로 갈아타는 단계가 포인터에 대한 개념에서 입니다. 그만큼 내용 이해가 어렵지만 포인터를 빼놓고 C언어 프로그램을 작성하는 것은 모래위에 집을 짓는 것보다 더 위험한 것일 겁니다.

처음 듣는 어려운 개념을 가장 빠르게 배우는 방법은 무엇일까요? 당연히 자주 써보는 겁니다. 포인터를 이해하려고 개념을 책으로만 공부하다보면 이책 저책 같은 개념을 다른 방식으로 설명을 해 놓아서 더욱 더 미궁으로 빠지기 쉽습니다. 포인터도 마찬가지입니다. 일단 몇가지 개념을 공부한 다음에 코딩을 해보면서 당연하게 여기게 될때까지 하다보면 자연스럽게 이해가 됩니다.

 

포인터? 도대체 너 누구니?

포인터(Pointer)는 간단히 말해 메모리 주소(Memory Address)를 저장하는 변수입니다. 컴퓨터에서 프로그램과 정보를 저장하는 내부 메모리(Internal Memory)가 있다는 건 잘 아시죠? 이 메모리는 선형적으로 구성이 되어 있는데 컴퓨터의 데이터 처리단위는 Byte이므로 8bit단위로 일렬로 구성되어 있고 각각의 Byte는 순서적으로 번호가 매겨져 있는데 얘를 주소(Address)라고 합니다. 이 주소를 저장하고 있는 변수를 포인터라고 하는 겁니다.

포인터 변수의 선언은 다음과 같이 합니다.

int *iptr;

위 변수의 의미는 "iptr이라는 변수가 int형을 가리키는 포인터"를 의미합니다. 즉, iptr이 int형이라는 말이 아니라 int형 자료를 가리키는 주소라는 말입니다. 일반적인 변수 선언에 '*'연산자를 붙여 선언을 한게 전부입니다. 여기서 기억해야할 점은 iptr도 변수이므로 메모리에 저장된다는 사실입니다. 아래 그림을 보시죠.

실제 시스템에서는 정수형 변수를 4Byte등으로 잡기 때문에 이 그림은 그냥 개념적으로만 보시길 바랍니다. 우선 iptr이라는 정수형을 가리키는 포인터를 선언하면 메모리 어딘가에 (0x11번지)에 포인터 변수가 잡히고 초기화가 안되어 있으므로 쓸데없는 값이 들어 있습니다.

그러면 이걸로 뭘 어쩌자는 걸까요? 먼저 예제를 보겠습니다.

#include <stdio.h>

void main()
{
    int ival;
    int *iptr;
    
    ival = 1;
    iptr = &ival;
    *iptr = 2;
    printf("ival = %d\n", ival);
}

 변수를 2개 선언했는데 첫번째 변수는 정수형(int) ival이라는 이름이고 두번째는 iptr이라는 정수형을 가리키는 포인터입니다. 다음에 ival에 1의 값을 대입했습니다.

그 다음에 iptr = &ival;이란 문장은 '&'라는 문자를 기본 자료형 변수앞에 붙여주면 주소를 의미하게 됩니다. 따라서 이문장은 ival의 주소를 iptr변수에 대입하라는 의미입니다. 여기까지는 다음 그림과 같이 되겠습니다. iptr의 값으로 ival의 주소를 갖고 있네요.

그 다음 문장은 *iptr = 2; 인데요. 포인터 변수에서 *연산자는 그 주소가 가리키는 내용(값) 즉 ival의 주소의 내용(값)을 의미하므로 iptr이 가리키는 주소의 내용(값)에 2를 대입하라는 의미입니다. 

마지막으로 ival의 값을 printf로 출력을 했는데 어떤 값이 나올까요? 반드시 프로그램을 한번 돌려서 확인해 보세요.

 

포인터. 그래서 뭐?

일단 잘 알려진 변수의 값을 교환하는 함수를 보겠습니다. 우선 C언어에서 함수의 인수를 전달할 때 그 인수 자체를 넘기지 않고 인수의 복사본(Copy)을 즉 값만 전달합니다.

 

#include <stdio.h>

void swapint(int a, int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void main ()
{
   int x, y;
   x = 1;
   y = 2;
   swapint(x, y);
   printf ("(x, y) = (%d, %d)\n", x, y);
}

원하는 결과를 x와 y의 값이 변경되는 것이지만 변수 x와 y는 main함수의 변수이고 변수 a와 b swapint의 변수로 값만 복사된 것이므로 다른 변수입니다. 

swapint로 값을 넘겼을 때 메모리

위와 같이 변수들이 서로 다른 메모리에 각각 잡히게 됩니다. 실제 swapint에서 값을 교환할 때는 x, y변수가 아닌 a, b변수의 값을 교환합니다. 그러므로 swapint에서 값을 교환한 다음의 메모리는 다음과 같습니다.

swapint에서 값을 교환한 다음 메모리

메모리 상태를 보니 원하는 결과가 아닙니다. 이럴 때 포인터의 사용이 빛을 발하게 되죠. 변수를 교환하는 코드를 다음과 같이 수정을 하겠습니다. swapint의 인수로 x, y의 주소인 포인터 변수를 넘겼습니다. swapint에서는 *연산자를 이용해 주소가 가리키는 값을 수정을 했구요.

#include <stdio.h>

void swapint(int *px, int *py)
{
    int temp;
    temp = *px;
    *px = *py;
    *py = temp;
}

void main ()
{
   int x, y;
   x = 1;
   y = 2;
   swapint(&x, &y);
   printf ("(x, y) = (%d, %d)\n", x, y);
}

실제 swapint함수에서 넘겨받은 주소는 각각 x, y의 실제 메모리 주소이고 메모리 주소가 가리키는 값을 교환하는 코드이므로 x, y의 값을 바꿀 수 있었습니다.

여기까지 오니 머리에 김이 나는 것 같죠? 그림을 잘 살펴보면서 마지막 코드를 그림으로 그린다면 어떻게 할 지 고민해 보아요. 

다음 시간에는 조금 더 깊게 포인터를 파헤쳐 봅시다. 안녕~

반응형

댓글