31.2 역참조 연산자 사용하기

포인터 변수에는 메모리 주소가 저장되어 있습니다. 이때 메모리 주소가 있는 곳으로 이동해서 값을 가져오고 싶다면 역참조(dereference) 연산자 *를 사용합니다.

  • *포인터

dereference.c

#include <stdio.h>

int main()
{
    int *numPtr;      // 포인터 변수 선언
    int num1 = 10;    // 정수형 변수를 선언하고 10 저장

    numPtr = &num1;   // num1의 메모리 주소를 포인터 변수에 저장

    printf("%d\n", *numPtr);    // 10: 역참조 연산자로 num1의 메모리 주소에 접근하여 값을 가져옴

    return 0;
}

실행 결과

10

역참조 연산자 *는 포인터 앞에 붙입니다. 다음과 같이 numPtr 앞에 *를 붙이면 numPtr에 저장된 메모리 주소로 가서 값을 가져옵니다. 여기서는 numPtrnum1의 메모리 주소를 저장하고 있으므로 num1의 값인 10이 출력됩니다.

printf("%d\n", *numPtr);    // 10: 역참조 연산자로 num1의 메모리 주소에 접근하여 값을 가져옴

즉, 포인터는 변수의 주소만 가리키며 역참조는 주소에 접근하여 값을 가져옵니다.

그림 31-6 역참조 연산자를 사용하여 메모리 주소의 값을 가져옴
참고 | 포인터 선언과 역참조?

포인터를 선언할 때도 *를 사용하고 역참조를 할 때도 *를 사용합니다. 같은 * 기호를 사용해서 헷갈리기 쉽지만 선언과 사용을 구분해서 생각하면 됩니다. 즉, 포인터를 선언할 때 *"이 변수가 포인터다"라고 알려주는 역할이고, 포인터에 사용할 때 *"포인터의 메모리 주소를 역참조하겠다"라는 뜻입니다.

int *numPtr;                // 포인터. 포인터를 선언할 때 *
printf("%d\n", *numPtr);    // 역참조. 포인터에 사용할 때 *

이번에는 포인터 변수에 역참조 연산자를 사용한 뒤 값을 저장(할당)해보겠습니다.

  • *포인터 = 값;

dereference_assign.c

#include <stdio.h>

int main()
{
    int *numPtr;      // 포인터 변수 선언
    int num1 = 10;    // 정수형 변수를 선언하고 10 저장

    numPtr = &num1;   // num1의 메모리 주소를 포인터 변수에 저장

    *numPtr = 20;     // 역참조 연산자로 메모리 주소에 접근하여 20을 저장

    printf("%d\n", *numPtr);    // 20: 역참조 연산자로 메모리 주소에 접근하여 값을 가져옴
    printf("%d\n", num1);       // 20: 실제 num1의 값도 바뀜

    return 0;
}

실행 결과

20
20

역참조 연산자는 값을 가져올 수도 있고 값을 저장할 수도 있습니다. 여기서는 *numPtr = 20;과 같이 numPtr에 저장된 메모리 주소에 접근하여 20을 저장했습니다. 따라서 printf*numPtr을 출력해보면 20이 나옵니다.

또 한가지 중요한 점은 *numPtr = 20;으로 20을 저장한 뒤 printf로 변수 num1의 값을 출력해보면 20이 나온다는 것입니다. 왜냐하면 numPtr에는 num1의 메모리 주소가 저장되어 있으므로 역참조 연산자로 값을 저장하면 결국 num1에 저장하게 됩니다.

그림 31-7 역참조 연산자를 사용하여 메모리 주소에 값 저장

역참조 연산자는 자료형을 바꾸는 효과를 냅니다. 즉, int *numPtr;에서 *numPtr처럼 역참조하면 pointer to int에서 pointer to를 제거하여 그냥 int로 만듭니다(int 포인터 → int).

만약 포인터 numPtr에 변수 num1을 할당한다면 역참조 연산자로 자료형을 맞춰주면 됩니다.

int *numPtr;
int num1 = 10;

numPtr = num1;   // 컴파일 경고, numPtr은 int 포인터형이고 num1은 int형이라 자료형이 일치하지 않음

*numPtr = num1;  // *numPtr은 int형이고 num1도 int형이라 자료형이 일치함

이 코드를 컴파일하면 다음과 같은 경고가 표시되며 Visual Studio에서는 화면 하단의 출력 탭에서 확인할 수 있습니다(만약 출력 탭이 없다면 메뉴에서 보기(V) > 출력(O)을 클릭하면 됩니다).

컴파일 결과

warning C4047: '=': 'int *'의 간접 참조 수준이 'int'과(와) 다릅니다.

여기서 int *의 간접 참조 수준이 int와 다르다고 나오는 것은 numPtrnum1이 서로 자료형이 다르기 때문입니다. 따라서 *numPtr = num1;과 같이 int 포인터를 역참조하면 int와 자료형이 같아지므로 컴파일 경고가 발생하지 않습니다.

물론 주소 연산자 &도 자료형을 맞춰주는 역할을 합니다(&를 붙이지 않으면 앞과 같은 경고가 발생합니다).

int *numPtr;
int num1;

numPtr = &num1;    // numPtr은 int 포인터형이고, &num1은 int형 변수의 주소이므로 자료형이 일치함
                   // numPtr은 pointer to int, &num1은 address of int이므로 자료형이 일치함

즉, pointer to int와 address of int는 자료형이 같습니다.

그림 31-8 포인터 자료형

이제 변수, 주소 연산자, 역참조 연산자, 포인터의 차이를 정리해보겠습니다.

그림 31-9 변수, 주소 연산자, 역참조 연산자, 포인터의 차이

먼저 변수는 메모리 주소를 몰라도 값을 가져오거나 저장할 수 있습니다. 그냥 변수에 값을 할당하거나 그대로 출력하면 됩니다.

주소 연산자 &는 변수의 메모리 주소를 구합니다. 이 그림에서 10을 감싸고 있는 상자는 메모리 공간을 뜻하는데 주소 연산자는 메모리 공간이 어디에 있는지 위치만 알아낼 수 있습니다.

역참조 연산자 *는 그림에서 보면 상자 안까지 들어가서 값을 가져오거나 저장합니다. 즉, 메모리 주소를 알고 있으므로 메모리 주소를 거쳐서 그 안에 있는 값을 가져오거나 저장합니다.

마지막으로 포인터는 변수의 메모리 주소만 가리킵니다. 따라서 포인터는 메모리 공간이 어디에 있는지 위치만 알고 있습니다.