44.2 포인터 연산과 역참조 사용하기

포인터 연산으로 조작한 메모리 주소도 역참조 연산을 사용하여 메모리에 접근할 수 있습니다. 다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

pointer_dereference.c

#include <stdio.h>

int main()
{
    int numArr[5] = { 11, 22, 33, 44, 55 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;

    numPtrA = numArr;    // 배열 첫 번째 요소의 주소를 포인터에 저장

    numPtrB = numPtrA + 1;    // 포인터 연산. numPtrA + 4바이트
    numPtrC = numPtrA + 2;    // 포인터 연산. numPtrA + 8바이트

    printf("%d\n", *numPtrB);    // 22: 역참조로 값을 가져옴, numArr[1]과 같음
    printf("%d\n", *numPtrC);    // 33: 역참조로 값을 가져옴, numArr[2]와 같음

    return 0;
}

실행 결과

22
33

numPtrB에는 numPtrA에 1을 더해서 4바이트만큼 순방향으로 이동한 메모리 주소를 저장했고, numPtrC에는 numPtrA에 2를 더해서 8바이트만큼 순방향으로 이동한 메모리 주소를 저장했습니다.

numPtrB = numPtrA + 1;    // 포인터 연산. numPtrA + 4바이트
numPtrC = numPtrA + 2;    // 포인터 연산. numPtrA + 8바이트

numPtrBnumPtrC도 일반 포인터이므로 * (역참조 연산자)를 사용하여 메모리의 값을 가져올 수 있습니다. 여기서 포인터 연산은 결과적으로 numPtrA + 1numArr[1]은 같고, numPtrA + 2numArr[2]는 같습니다.

printf("%d\n", *numPtrB);    // 22: 역참조로 값을 가져옴, numArr[1]과 같음
printf("%d\n", *numPtrC);    // 33: 역참조로 값을 가져옴, numArr[2]와 같음

이번에는 포인터 연산과 동시에 역참조 연산을 사용하는 방법을 알아보겠습니다. 다음과 같이 포인터 연산을 한 부분을 ( ) 괄호로 묶어준 뒤 맨 앞에 * (역참조 연산자)를 붙이면 됩니다.

  • *(포인터 + 값)
  • *(포인터 - 값)

다음 내용을 소스 코드 편집 창에 입력한 뒤 실행해보세요.

pointer_add_dereference.c

#include <stdio.h>

int main()
{
    int numArr[5] = { 11, 22, 33, 44, 55 };
    int *numPtrA;

    numPtrA = numArr;    // 배열 첫 번째 요소의 주소를 포인터에 저장

    printf("%d\n", *(numPtrA + 1));    // 22: numPtrA에서 순방향으로 4바이트만큼 떨어진
                                       // 메모리에 주소에 접근. numArr[1]과 같음

    printf("%d\n", *(numPtrA + 2));    // 33: numPtrA에서 순방향으로 8바이트만큼 떨어진
                                       // 메모리에 주소에 접근. numArr[2]와 같음

    return 0;
}

실행 결과

22
33

*(numPtrA + 1)와 같이 numPtrA에 1을 더한 뒤 괄호로 묶어줍니다. 그리고 맨 앞에 역참조 연산자를 붙여주면 numPtrA에서 순방향으로 4바이트만큼 떨어진 메모리 주소에서 값을 가져올 수 있습니다. 여기서 포인터 연산과 역참조 연산 *(numPtrA + 1)은 결과적으로 numArr[1]과 같습니다.

printf("%d\n", *(numPtrA + 1));    // 22: numPtrA에서 순방향으로 4바이트만큼 떨어진
                                   // 메모리에 주소에 접근. numArr[1]과 같음

printf("%d\n", *(numPtrA + 2));    // 33: numPtrA에서 순방향으로 8바이트만큼 떨어진
                                   // 메모리에 주소에 접근. numArr[2]와 같음

만약 포인터 연산을 괄호로 묶어주지 않으면 역참조 연산자가 먼저 실행되어 값을 가져온 뒤 덧셈(뺄셈) 연산을 하게 됩니다. 따라서 다음 코드는 numPtrA를 역참조하여 11을 가져온 뒤 1을 더하므로 12가 됩니다.

printf("%d\n", *numPtrA + 1);    // 12: numPtrA를 역참조하여 나온 값에 1을 더하므로 12

즉, 역참조 연산보다 포인터 연산이 먼저 실행될 수 있도록 괄호로 묶어주는 것입니다.

그림 44‑4 포인터 연산과 괄호

이번에는 증가 연산자와 역참조 연산자를 사용해보겠습니다.

  • *(++포인터)
  • *(--포인터)

pointer_inc_dec_dereference.c

#include <stdio.h>

int main()
{
    int numArr[5] = { 11, 22, 33, 44, 55 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;

    numPtrA = &numArr[2];    // 배열 세 번째 요소의 주소를 포인터에 저장

    numPtrB = numPtrA;
    numPtrC = numPtrA;

    printf("%d\n", *(++numPtrB));    // 44: numPtrA에서 순방향으로 4바이트만큼 떨어진
                                     // 메모리 주소에 접근
    printf("%d\n", *(--numPtrC));    // 22: numPtrA에서 역방향으로 4바이트만큼 떨어진
                                     // 메모리 주소에 접근

    return 0;
}

실행 결과

44
22

*(++numPtrB)와 같이 변수 앞에 증가 연산자를 사용한 뒤 괄호로 묶어줍니다. 그리고 맨 앞에 역참조 연산자를 붙여주면 numPtrB에 저장된 메모리 주소가 4바이트만큼 증가하고, 해당 메모리의 값을 가져옵니다. 마찬가지로 *(--numPtrC)처럼 감소 연산자와 역참조 연산자를 사용하면 numPtrC에 저장된 메모리 주소가 4바이트만큼 감소하고, 해당 메모리의 값을 가져옵니다.

printf("%d\n", *(++numPtrB));    // 44: numPtrA에서 순방향으로 4바이트만큼 떨어진
                                 // 메모리 주소에 접근
printf("%d\n", *(--numPtrC));    // 22: numPtrA에서 역방향으로 4바이트만큼 떨어진
                                 // 메모리 주소에 접근

만약 증감 연산자를 변수 앞에 사용할 때 괄호로 묶어주지 않아도 동작은 같습니다(전위 증감 연산자와 역참조 연산자는 우선순위가 같고 결합방향이 오른쪽에서 왼쪽이기 때문).

printf("%d\n", *++numPtrB);    // 44
printf("%d\n", *--numPtrC);    // 22

증가, 감소 연산자를 변수 뒤에 붙이고 포인터 연산을 하면 현재 메모리의 값을 가져온 뒤 포인터 연산을 하므로 주의해야 합니다.

printf("%d\n", *(numPtrB++));    // 33: numPtrA의 메모리에 접근하여 값을 가져온 뒤 포인터 연산
printf("%d\n", *(numPtrC--));    // 33: numPtrA의 메모리에 접근하여 값을 가져온 뒤 포인터 연산