포스트

[C++] 포인터와 레퍼런스(참조)의 차이

C 언어는 데이터 원본에 접근하기 위해 포인터 를 사용합니다.
그러나 포인터는 몇 가지 위험성을 가지고 있기 때문에 이 위험성을 줄이고자 C++ 에서는 참조 를 사용합니다.

먼저 포인터의 위험성을 확인한 뒤 C++ 참조 연산자와 무엇이 다른지 확인해보겠습니다.

포인터 개념 짚고 가기

포인터는 메모리 주소 값 을 저장하는 변수입니다.
어떤 데이터 타입의 메모리 주소 값을 담을 것인가에 따라 선언하는 자료형은 달라집니다.
예를들어, int 형 데이터의 주소를 담으려면 int* 로 선언하고
char 형 데이터 주소를 담으려면 char* 로 선언합니다.
그리고 포인터 변수에 값을 념겨줄 때는 & 연산자를 앞에 붙여 념겨줍니다.


포인터의 위험성

포인터 변수는 C언어의 강력한 기능 중 하나로 메모리 주소를 사용한다는 특징이 있습니다.
그러나 강력한 기능인 만큼 잘못 사용할 경우, 프로그램에 치명적인 오류가 될 수 있습니다.

첫 번째로, 저장한 메모리 주소가 nullptr 인 경우 입니다.
nullptr 은 메모리에 할당된 주소가 없다는 뜻으로 데이터가 유효하지 않습니다.
따라서 nullptr 을 담고 있는 포인터 변수를 사용할 경우 프로그램에 치명적일 수 있습니다.

두 번째로, 메모리 주소 자체를 담기 때문에 잘못된 주소로 접근할 수 있다는 점입니다.
포인터 변수는 +, - 연산자를 사용할 수 있기 때문에 (ptr + 1), (ptr - 10) 등
현재 주소에서 다른 주소로 이동할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// int형 포인터
int value_01 = 10;
int* ptr_01 = &value_01;
// *ptr_01은 10을 담고, ptr_01은 value_01의 주소를 저장

// char형 포인터
char value_02 = 'C';
char* ptr_02 = &value_02;
// *ptr_02은 문자 C를 담고, ptr_02은 value_02의 주소를 저장

printf("%p\n", ptr_02); // 주소를 출력
printf("%c", *ptr_02);  // 값을 출력

// 현재 주소를 임의로 변경할 수 있어 위험
ptr_01 += 10;
ptr_02 += 20;


따라서 C++ 는 메모리 접근에 대한 위험성을 줄이고자 참조를 사용합니다.

참조자 개념 짚고 가기

참조자는 데이터의 원본에 접근하기 위해서 사용합니다.
참조자 또한 포인터처럼 어떤 데이터의 원본을 담을 것인가에 따라 선언 형식이 달라집니다.
예를들어, int 형 데이터의 원본을 담고 싶으면 int& 를 사용하고
char 형 데이터의 원본을 담으려면 char& 를 사용합니다.


여러 참조자 활용 방법 중 아래의 코드처럼 함수에 인자를 넘길 때 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void make_double_vector(vector<int>& vec) {
    for(int& n : vec) {
        n *= 2;
    }
}

int main() {
    vector<int> vec{1, 2, 3, 4, 5};
    
    cout << "prev vector: ";
    for(int n : vec) {
    	cout << n << ", ";
    }
    cout << endl;
    
    make_double_vector(vec);
    
    cout << "double vector: ";
    for(int n : vec) {
    	cout << n << ", ";
    }
    cout << endl;
    return 0;
}

참조자가 권장되는 이유

첫 번째로 위의 코드처럼 함수의 인수를 참조자로 사용할 경우, 복사 연산자를 사용하지 않고
vec 변수 원본에 접근해 값을 사용하기 때문에 새로운 메모리 공간을 확보할 필요가 없습니다.

두 번째는 포인터는 nullptr 을 할당할 수 있지만
참조자는 포인터에서 nullptr 가 할당된 채로 사용되는 위험성을 제거하고자 NULL, nullptr 과 같이
유효하지 않는 값을 할당할 수 없도록 되어있습니다.

마지막으로 참조자는 사용하는 문자가 적고 직관적이기 때문에 포인터보다 가독성이 좋습니다.

포인터가 사용되는 특수한 상황

int* arr = new int[10];

참조자는 원본 데이터 접근에 사용되지만
포인터는 원본 데이터 접근만 아니라 동적 메모리 관리, 배열 혹은 null 을 사용하는 데이터에서 사용될 수 있기 때문에
가능하면 참조자를 사용하고, 필요할 때만 포인터를 사용하는 것이 좋습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.