44.1 포인터 연산으로 메모리 주소 조작하기

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

  • 포인터 + 값
  • 포인터 - 값

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

pointer_add.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;    // 포인터 연산
    numPtrC = numPtrA + 2;    // 포인터 연산
    
    printf("%p\n", numPtrA);    // 00A3FC00: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
    printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
    printf("%p\n", numPtrC);    // 00A3FC08: sizeof(int) * 2이므로 numPtrB에서 8이 증가함

    return 0;
}

실행 결과

00A3FC00 (메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐)
00A3FC04
00A3FC08

먼저 정수형 배열을 선언하고 값을 초기화 했습니다. 그리고 배열을 포인터에 할당했습니다(배열 이름 자체는 배열의 첫 번째 요소에 대한 포인터라 할 수 있습니다).

포인터 연산은 특별한 것이 없습니다. 포인터 변수에 그냥 정수 값을 더하거나 빼면 됩니다. 단, 연산하는 값이 메모리 주소이므로 곱하거나 나누는 연산은 의미가 없습니다(컴파일 에러 발생).

numPtrB = numPtrA + 1;    // 포인터 연산
numPtrC = numPtrA + 2;    // 포인터 연산

그런데 연산한 결과가 조금 이상하죠? numPtrA에 저장된 메모리 주소 00A3FC00에 1을 더하면 1이 증가한 00A3FC01이 나와야 할 것 같은데 00A3FC04가 나왔습니다. 또한, 00A3FC00에 2를 더하면 2가 증가한 00A3FC02가 나와야 할 것 같지만 00A3FC08이 나옵니다(주솟값은 컴퓨터마다, 실행할 때마다 달라짐). 즉, 포인터 연산은 포인터 자료형의 크기만큼 더하거나 뺍니다.

printf("%p\n", numPtrA);    // 00A3FC00: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC);    // 00A3FC08: sizeof(int) * 2이므로 numPtrB에서 8이 증가함

여기서는 numPtrA가 4바이트 크기의 int형입니다. 따라서 numPtrA + 1은 메모리 주소에서 4바이트만큼 1번 순방향으로 이동한다는 뜻(포인터 전진, forward)이고, numPtrA + 2는 메모리 주소에서 4바이트만큼 2번 순방향으로 이동한다는 뜻입니다. 즉, 계산식은 "sizeof(자료형) * 더하거나 빼는 값"이 됩니다.

그림 44‑2 포인터 덧셈 연산과 메모리 주소

이번에는 포인터에서 뺄셈을 해보겠습니다.

pointer_sub.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 - 1;    // 포인터 연산
    numPtrC = numPtrA - 2;    // 포인터 연산
    
    printf("%p\n", numPtrA);    // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
    printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * -1이므로 numPtrA에서 4가 감소함
    printf("%p\n", numPtrC);    // 00A3FC00: sizeof(int) * -2이므로 numPtrB에서 8이 감소함

    return 0;
}

실행 결과

00A3FC08 (메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐)
00A3FC04
00A3FC00

numPtrA = &numArr[2];와 같이 배열에 [ ](대괄호)를 사용하여 요소에 접근한 뒤 &(주소 연산자)를 사용하면 해당 요소의 메모리 주소를 구할 수 있습니다. 여기서는 numArr에서 세 번째 요소의 메모리 주소를 구해서 numPtrA에 저장했습니다.

포인터 연산으로 numPtrA에서 1, 2를 뺍니다.

numPtrB = numPtrA - 1;    // 포인터 연산
numPtrC = numPtrA - 2;    // 포인터 연산

numPtrBnumPtrA에서 4가 감소한 값이 나오고, numPtrCnumPtrA에서 8이 감소한 값이 나옵니다. 즉, sizeof(int) * -1이므로 메모리 주소에서 4바이트만큼 역방향으로 이동(포인터 후진, backward)하고, sizeof(int) * -2이므로 메모리 주소에서 8바이트만큼 역방향으로 이동합니다.

printf("%p\n", numPtrA);    // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * -1이므로 numPtrA에서 4가 감소함
printf("%p\n", numPtrC);    // 00A3FC00: sizeof(int) * -2이므로 numPtrB에서 8이 감소함
그림 44‑3 포인터 뺄셈 연산과 메모리 주소
참고 | char, short, long long 포인터를 연산하면?

포인터 연산은 "sizeof(자료형) * 더하거나 빼는 값"이므로 char는 1바이트, short는 2바이트, long long은 8바이트만큼 메모리 주소에서 순방향, 역방향으로 이동합니다.

pointer_add_char_short_long_long.c

#include <stdio.h>

int main()
{
    char *cPtr1 = NULL;
    short *numPtr1 = NULL;
    long long *numPtr2 = NULL;

    printf("%p\n", cPtr1 + 1);      // 00000001: 0x000000에서 1바이트만큼 순방향으로 이동
    printf("%p\n", numPtr1 + 1);    // 00000002: 0x000000에서 2바이트만큼 순방향으로 이동
    printf("%p\n", numPtr2 + 1);    // 00000008: 0x000000에서 8바이트만큼 순방향으로 이동

    return 0;
}

실행 결과

00000001
00000002
00000008

이번에는 증가, 감소 연산자를 사용해보겠습니다.

  • 포인터++
  • 포인터--
  • ++포인터
  • --포인터

pointer_increment_decrement.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;

    numPtrB++;    // 포인터 연산
    numPtrC--;    // 포인터 연산

    printf("%p\n", numPtrA);    // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
    printf("%p\n", numPtrB);    // 00A3FC0C: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
    printf("%p\n", numPtrC);    // 00A3FC04: sizeof(int) * -1이므로 numPtrB에서 4가 감소함

    return 0;
}

실행 결과

00A3FC08
00A3FC0C
00A3FC04

먼저 numPtrBnumPtrCnumPtrA의 메모리 주소를 저장했습니다. 그리고 numPtrB에는 증가 연산자를 사용하고, numPtrC에는 감소 연산자를 사용했습니다. 메모리 주소는 어떻게 될까요?

증가 연산자는 + 1과 같습니다. 따라서 sizeof(int) * 1이 되므로 numPtrA에서 4가 증가합니다. 마찬가지로 감소 연산자는 - 1과 같으므로 sizeof(int) * -1이고 numPtrB에서 4가 감소합니다.

numPtrB++;    // 포인터 연산
numPtrC--;    // 포인터 연산

printf("%p\n", numPtrA);    // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB);    // 00A3FC0C: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC);    // 00A3FC04: sizeof(int) * -1이므로 numPtrB에서 4가 감소함