본문 바로가기

프로그래밍/C++

Reference(참조) vs. Pointer(포인터) in C++

C++로 dp관련 알고리즘 문제를 풀 때, 아래와 같이 참조(reference)를 종종 이용하곤 했다.

void sol(int i,int j){
	int& ref=s[i][j];
    ...
}

포인터와 유사한 듯한 이 참조는 포인터와 비교하여 어떤 차이가 있는지 궁금하여 알아보았다.

 

Pointer

우선, 포인터는 다른 변수의 메모리 주소를 가지고 있는 변수로, 아래와 같이 사용한다.

int a = 10;
int *p = &a;

포인터가 가리키는 메모리 위치에 접근하기 위해서는 * 연산자(operator)를 사용하여 디레퍼런스(dereference)하는 과정이 필요하다.

 

Reference

반면에, 참조 변수는 이미 존재하는 변수의 별칭(alias)이다. 내부적으로는 포인터와 같이 객체의 주소를 저장하도록 구현되었다. 한번 초기화되면 참조 객체를 변경할 수 없다는 점에서 const pointer로 볼 수도 있다. 

int a = 10;
int &p = a;

 

차이점

이러한 포인터와 참조의 비교를 아래 표로 정리해보았다.

Pointer Reference
선언한 이후에 초기화할 수 있다. 반드시 선언과 동시에 초기화해야 한다.
NULL 값을 할당할 수 있고, 재할당(reassignment)이 가능하다. NULL을 참조할 수 없고, 재할당도 불가능하다.
operator를 사용하여 dereference해서 참조 변수에 접근한다. 단순히 이름만으로 접근이 가능하다.

참조 변수의 제약 때문에 포인터를 사용하면 맞닥뜨릴 수 있는 예외 상황이 없다는 점에서, 참조변수가 포인터에 비해 유연하진 않지만 안전하다.

 

근데, 메모리에 데이터는 어떻게 쌓일까?

개념을 정리하다보니 위 질문에 대한 답이 궁금해졌다. 참조는 내부적으로 포인터와 유사하게 동작한다고 하니 메모리 스택에 메모리 공간이 할당되고, 주소값이 할당될 것이다. 이 가설을 증명하기 위해 에디터(Xcode)를 사용해서 직접 확인해보았다.

 

변수 a를 123값으로 초기화하여 선언하고, 포인터 변수 ptr을 선언하여 a를 참조하도록 했다. 유사하게 b와 참조변수 ref를 선언하였다.

int a=123;
int* ptr=&a;

int b=456;
int& ref=b;

포인터 변수 ptr에 저장된 값은 a의 주소값이다. 이는 아래와 같은 코드로 확인할 수 있다.

cout<<&a<<' '<<ptr<<endl;
// 0x7ffeefbff3a8 0x7ffeefbff3a8

 또한, 참조 변수 ref에 저장된 값은 b의 주소값일 것이다. 하지만 이건 어떻게 코드로 확인할 수 있을까? 어셈블리 코드의 도움이 필요한 시점이다. (Xcode의 Assistant로 어셈블리 변환 코드를 쉽게 확인할 수 있다.)

int a=123;
// movl    $123, -8(%rbp)
int* ptr=&a;
// leaq    -8(%rbp), %rax
// movq    %rax, -16(%rbp)

int b=456;
// movl    $456, -20(%rbp)
int& ref=b;
// leaq    -20(%rbp), %rax
// movq    %rax, -32(%rbp)

포인터 변수와 참조 변수의 선언부를 어셈블리로 변환한 코드는 동일한 형태를 보인다. (컴파일러에 따라 변환된 어셈코드는 상이하다.) 즉, 참조 변수가 내부적으로 포인터 변수처럼 동작한다는 증명이 된 것이다. (포인터 변수와 다르게 참조 변수는 컴파일 시에 자동으로 dereference되어 참조하는 변수에 접근하게 된다.)

 

어셈블리어 코드에서 b와 ref의 주소값 차이를 보면 12임을 알 수 있고, 포인터의 포인터로 접근하여 타입캐스팅을 해주면, 참조 변수 ref에 저장된 값이 b의 주소값이라는 것은 아래 코드로 확인할 수 있다.

cout<<&b<<' '<<*(int**)(&b-3)<<endl;
// 0x7ffeefbff39c 0x7ffeefbff39c

즉, 참조 변수도 포인터 변수와 동일하게 같은 크기의 메모리 공간을 사용하여 참조하는 변수의 주소값을 저장한다.