본문 바로가기

C++/C, C++, STL

포인터(Pointer)와 주소 연산자(&), 역참조 연산자(*) 그리고 주소의 증감 연산 처리

이 문서의 내용

    포인터란?

    변수(Variables)는 어떤 데이터를 저장하기 위해 메모리에 공간을 할당 받습니다. 이때 메모리의 시작 주소를 주소 값이라고 부릅니다.

    예를 들어 다음과 같이 int a 변수를 선언하면 4바이트 메모리 공간을 할당 받고 이때 주소 값은 0x0001입니다.

    마찬가지로 short b 변수는 2바이트를 사용하며 주소 값은 0x0006입니다. 

    이를 코드에서 확인하려면 다음과 같이 작성합니다.

    int a = 1234567;
    
    std::cout << &a;

    프로그램을 실행하면 다음과 같이 출력됩니다.

    0x61ff0c

    이때 &a는 변수 a에 대한 주소를 의미하며 &를 주소 연산자라고 부릅니다.

    포인터(Pointer)는 이처럼 변수에 할당되어 있는 메모리 주소 값을 저장하는 변수입니다.

    포인터 또한 하나의 변수이며 다음과 같이 선언 할 수 있습니다.

    [참조 변수 타입]* [변수이름] = &[참조 변수 이름]

    예를 들어 예시의 변수 int a의 주소 값을 저장하는 포인터 변수 ap는 다음과 같습니다.

    int a = 1234567;
    int* ap = &a;
    
    std::cout << &a << std::endl;
    std::cout << ap << std::endl;

    포인터 변수에는 참조 변수의 주소 값이 저장되어 있으므로, 예시의 출력 결과는 동일한 주소 값을 표시합니다.

    0x61ff0c
    0x61ff0c

    주소 연산자(&)

    앞서 소개한 주소 연산자 &는 어떤 변수에 할당된 메모리 주소를 확인하기 위해서 사용합니다.

    포인터 변수에는 참조하는 변수의 주소 값이 저장되어야 하므로, 포인터 변수에 다른 변수를 할당하는 구문에서는 & 연산자가 사용됩니다.

    int a = 1234567;
    int* ap = &a;

    역참조 연산자(*)

    메모리 주소 값을 알고있으면 해당 메모리에 저장된 값을 읽을 수 있습니다.

    이때 사용되는 키워드가 역참조 연산자 *입니다.

    int a = 1234567;
    
    std::cout << a << std::endl;
    std::cout << *&a << std::endl;

    & 연산자가 포인터에 다른 변수의 주소 값을 저장하기 위해 사용된다면 * 연산자는 포인터가 가리키는 주소 값에 실제 저장된 값에 접근하기 위해 사용됩니다.

    int a = 1234567;
    int* ap = &a;
    
    // output: 1234567
    std::cout << *ap << std::endl;

    포인터 사용 예시

    포인터에서 가장 중요한 키워드는 포인터 변수가 가리키는 메모리 주소의 값을 * 연산자로 접근 가능하다는 점입니다.

    그리고 메모리 주소 값은 특정 포인터와 1:1로 매핑되는 것은 아닙니다.

    int a = 1000;
    int* ap0 = &a;
    int* ap1 = &a;
    
    // handle origin variable
    a = 2000;
    std::cout << a << " " << *ap0 << " " << *ap1 << std::endl;
        
    // handle pointer 0
    *ap0 = 3000;
    std::cout << a << " " << *ap0 << " " << *ap1 << std::endl;
        
    // handle pointer 1
    *ap1 = 4000;
    std::cout << a << " " << *ap0 << " " << *ap1 << std::endl;

    예시에서 변수 int a의 주소 값을 저장하는 두 개의 포인터 변수 ap0 ap1을 다루고 있습니다.

    프로그램을 실행하면 3개의 변수에서 실제 저장하는 값이 일치합니다.

    2000 2000 2000
    3000 3000 3000
    4000 4000 4000

    서로 다른 변수가 동일한 메모리 주소에 접근하기 때문에 가능한 일입니다.

    포인터는 예시처럼 여러 개의 포인터 변수를 사용하여 하나의 메모리 주소를 다루기 위해 사용됩니다.

    주소의 증감 연산

    포인터 변수에는 주소 값이 저장되어 있으며 주소 값은 증감 연산자로 다른 주소를 가리키는 것이 가능합니다.

    int arr[] = { 1, 2, 3, 4 };
    
    int* ap = &arr[0];
    
    std::cout << *ap << std::endl;
    std::cout << *(ap + 1) << std::endl;
    std::cout << *(ap + 2) << std::endl;
    std::cout << *(ap + 3) << std::endl;
    코드 비고
    Line 3:6 int* ap = &arr[0] 배열의 0번 인덱스가 사용하는 메모리 주소 값을 가리킵니다.
    Line 8:11 *ap0 배열의 0번 인덱스의 값에 접근합니다.
    *(ap + 1) 포인터가 가리키는 메모리 주소 값을 증가시켜 배열의 1번 인덱스 값에 접근합니다.
    *(ap + 2) 포인터가 가리키는 메모리 주소 값을 증가시켜 배열의 2번 인덱스 값에 접근합니다.
    *(ap + 3) 포인터가 가리키는 메모리 주소 값을 증가시켜 배열의 3번 인덱스 값에 접근합니다.

    포인터 변수의 선언자에는 가리키는 메모리 주소 값에 저장된 실제 데이터 타입이 포함됩니다.

    이는 포인터 변수가 저장하고 있는 주소 값에 증감 연산을 할 때 주소 값을 얼만큼 증감 시켜야하는지에 대한 힌트입니다.

    다음 예시를 통해 주소의 증감 연산 결과를 확인합니다.

    short a = 10;
    int b = 20;
    
    short* ap = &a;
    int* bp = &b;
    
    std::cout << ap << std::endl;
    std::cout << ap + 1 << std::endl;
    std::cout << ap + 2 << std::endl;
        
    std::cout << std::endl;
    std::cout << bp << std::endl;
    std::cout << bp + 1 << std::endl;
    std::cout << bp + 2 << std::endl;
    코드 비고
    Line 7:9 ap + 1 short 데이터 타입의 메모리 주소 값을 가리킵니다.
    따라서 각 증감 연산자에 의한 주소는 2씩 이동합니다.
    ap + 2
    Line 11:14 bp + 1 int 데이터 타입의 메모리 주소 값을 가리킵니다.
    따라서 각 증감 연산자에 의한 주소는 4씩 이동합니다.
    bp + 2

    프로그램을 실행하고 출력 결과를 확인합니다. 각 데이터 타입이 사용하는 메모리 크기만큼 주소가 이동하는 것을 알 수 있습니다.

    0x61ff06
    0x61ff08
    0x61ff0a
    
    0x61ff00
    0x61ff04
    0x61ff08

    정리 및 복습

    • 변수(Variables)는 어떤 데이터를 저장하기 위한 목적으로 사용합니다. 이를 위해 메모리 공간을 할당합니다.
    • 변수가 데이터 저장을 위해 할당된 메모리 공간의 시작 지점은 주소 값이라고 부릅니다.
    • 포인터(Pointer)는 주소 값을 저장 할 수 있는 변수입니다.
    • 포인터에 주소 값을 저장할 때는 가리키려는 변수에 대해서 주소 연산자 &를 사용합니다.
    • 포인터에 가리키는 주소 값에 대한 실제 데이터에 접근하려면 역참조 연산자 *를 사용합니다.
    • 포인터가 가리키는 주소 값은 증감 연산자 + -로 주소 값을 이동시킬 수 있습니다.
    • 주소 값은 포인터가 가리키는 실제 데이터 타입이 사용하는 메모리 크기만큼 이동합니다.