[C++] 포인터와 래퍼런스(참조)

비아베·2024년 6월 1일

C++

목록 보기
5/7

포인터란?

포인터 : 변수의 주소를 저장하는 특별한 타입의 변수. 주소값자체를 포인터라고 생각해도 좋다. 메모리의 주소를 가지고 있는 변수이기에 주소 값을 통한 메모리 접근을 한다.(간접 참조)

주소만을 저장할 수 있는 변수를 포인터 변수라고 하고, 일반적인 변수 선언과는 다르게 자료형에 표시를 붙여 선언한다. int p;는 int 형 변수의 주소를 저장하는 포인터 (변수)p를 선언한 것이다. 포인터, 포인터 변수 모두 똑같은 말이다.

int num = 10;
//int 형 포인터 변수
int *p = #

포인터 연산자

C++에서 포인터와 연관되어 사용되는 연산자는 아래와 같다.

  1. 주소 연산자(&)
  2. 참조 연산자(*)
  • 주소 연산자(&)
    주소 연산자는 변수의 이름 앞에 사용하며, 해당 변수의 주소값을 반환한다.
    '&'기호는 앰퍼샌드(ampersand)라고 읽으며, 번지 연산자라고도 부른다.

  • 참조 연산자(*)
    참조 연산자는 포인터의 이름이나 주소 앞에 사용하며, 포인터에 저장된 주소에 저장되어 있는 값을 반환한다.
    기호는 역참조 연산자로 에스크리터(asterisk operator)라고도 불린다.

C++에서 '*'기호는 사용하는 위치에 따라 다양한 용도로 사용된다.
이항 연산자로 사용하면 곱셈 연산으로, 포인터의 선언 시에도, 메모리에 접근할 때도 사용된다.

포인터의 동시 선언

포인터를 선언할때에는 주의점이 있는데 바로 아래의 사례이다.

int *ptr1, ptr2;

이 경우엔 포인터가 ptr1만 선언이 된다. ptr2는 int형 변수로서 선언이 되기때문에 두개를 선언하고 싶으면 아래처럼 작성해야한다.

int *ptr1, *ptr2;

포인터의 선언과 초기화

포인터를 선언한 후 참조 연산자(*)를 사용하기 전에 포인터는 반드시 초기화되어야 한다.
초기화하지 않은 채로 참조 연산자를 사용하게 되면, 어딘지 알 수 없는 메모리 장소에 값을 저장하는 것이 되기 때문이다.

이러한 동작은 매우 위험한 결과를 초래할 수도 있으며, 이렇게 발생한 오류는 디버깅하기도 매우 힘들다.

따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 좋다.

타입* 포인터이름 = &변수이름;
타입* 포인터이름 = &주소값;

위의 방식대로 선언하면 된다.

포인터의 참조

C++에서 선언된 포인터는 참조 연산자(*)를 사용하여 참조할 수 있다.
다음 예제는 포인터의 주소값과 함께 포인터가 가리키고 있는 주소값의 데이터를 참조하는 예제이다.

int x = 7;            // 변수의 선언

int *ptr = &x;      // 포인터의 선언

int **pptr = &ptr; // 포인터의 참조

또 다른 예제를 살펴보겠다.

int num1 = 1234;

double num2 = 3.14;

int* ptr_num1 = &num1;

double* ptr_num2 = &num2;

 
① cout << "포인터의 크기는 " << sizeof(ptr_num1) << "입니다." << endl;

② cout << "포인터 ptr_num1가 가리키고 있는 주소값은 " << ptr_num1 << "입니다." << endl;

③ cout << "포인터 ptr_num1가 가리키고 있는 주소에 저장된 값은 " << *ptr_num1 << "입니다." << endl;

cout << "포인터 ptr_num2가 가리키고 있는 주소값은 " << ptr_num2 << "입니다." << endl;

cout << "포인터 ptr_num2가 가리키고 있는 주소에 저장된 값은 " << *ptr_num2 << "입니다.";

실행결과는 아래와 같다.

포인터의 크기는 8입니다.

포인터 ptr_num1가 가리키고 있는 주소값은 0x7fff789fab54입니다.

포인터 ptr_num1가 가리키고 있는 주소에 저장된 값은 1234입니다.

포인터 ptr_num2가 가리키고 있는 주소값은 0x7fff789fab58입니다.

포인터 ptr_num2가 가리키고 있는 주소에 저장된 값은 3.14입니다.

예제의 ①번 라인에서는 sizeof 연산자를 사용하여 포인터 변수의 크기를 구하고 있다.

포인터 변수는 메모리에서 변수의 위치를 나타내는 주소를 다루는 변수이므로, 그 크기는 일반적으로 CPU에 따라 결정된다.

따라서 32비트 CPU에서는 1워드(word)의 크기가 4바이트이므로, 포인터 변수의 크기 또한 4바이트가 될 것이다. (워드란 CPU가 처리할 수 있는 데이터의 크기이다.)

포인터 변수의 크기는 CPU의 종류와 컴파일할 때 사용된 컴파일러의 정책에 따라서 달라질 수 있다.

또한, ②번과 ③번 라인에서처럼 포인터가 가리키는 변수의 타입에 따라 포인터의 타입도 같이 바꿔주고 있다.
포인터의 타입은 참조 연산자를 통해 값을 참조할 때, 참조할 메모리의 크기를 알려주는 역할을 하기 때문이다.

아래 그림은 char형 포인터와 int형 포인터가 각각 메모리 상에서 해당 타입의 변수를 가리키는 것을 보여준다.

래퍼런스(참조)란?

래퍼런스 : 래퍼런스 = 참조자. 참조자 즉 래퍼런스는 변수에 별명(별칭)을 하나 붙여주는 것이다. 즉, 변수에 별명을 하나 붙여주는 것이다. 변수 명을 통해서 메모리를 참조한다.(직접 참조)

int num = 10;

//num의 래퍼런스
int& r = num;

num이 변수의 이름이면, r은 num의 별명이라는 뜻이다.
num과 r은 동일한 메모리 공간을 참조한다.

포인터와 래퍼런스의 차이

1. NULL 초기화

포인터는 NULL 초기화를 할 수 있지만, 래퍼런스는 NULL 초기화가 할 수 없다. 래퍼런스는 반드시 선언과 동시에 초기화를 해야한다.

이러한 특성 때문에, 포인터는 가리킬 대상을 변경할 수 있지만, 래퍼런스는 불가능하다.

int main(){
	int num = 10;
    
    //int형 포인터 변수
    int *Pptr = nullptr;
    
    //num의 래퍼런스인 ref
    int &ref = nullptr; //에러

2. 메모리 공간의 소모

포인터는 주소 값을 저장하기 위해 별도의 매모리 공간을 소모하지만 반면에 래퍼런스는 같은 메모리 공간을 참조하므로 메모리 공간을 소모하지 않는다.


3. call by pointer / call by reference

1. call by pointer

매개변수로 함수 인자 전달 시, 메모리 소모가 일어나고, 값 복사가 발생된다.

void call_by_pointer(int* pNum1, int* pNum2)
{
	int a = *pNum1;
    
    *pNum1 = *pNum2;
    *pNum2 = a;
}

2. call by reference

메모리 소모가 없고, 값 복사 또한 발생하지 않는다.

void call_by_reference(int& rNum1, int& rNum2)
{
	int a = rNum1;
    
    rNum1 = rNum2;
    rNum2 = a;
}
profile
게임 개발자로서의 한걸음

0개의 댓글