기존 C 언어는 변수를 가리키기 위해 포인터를 사용해야 했습니다. 반면 C++ 은 포인터 뿐만아니라 변수를 가리키는 또다른 방식 중 하나인 Reference 를 지원합니다. Reference를 선언하기 위해서는 단순히 가리키고자 하는 타입 뒤에 & 를 붙이면 됩니다.
int main() {
int number = 5;
int &another_number = number; // 레퍼런스 초기화 (변수 type 뒤 &)
another_number = 10;
std::cout << "number = " << number << std::endl;
std::cout << "another_number = " << another_number << std::endl;
}
위 코드를 보면 number 를 5로 초기화 한 뒤 another_number 는 number 의 레퍼런스로 지정합니다.
이후 another_number 의 값을 변경한 뒤 두 변수의 값을 출력하면 다음과 같이 나옵니다.
number = 10
another_number = 10
reference 의 원리만 이해한다면 위 코드의 동작방식을 이해하기 수월합니다.
Reference 는 변수의 별칭 입니다. 즉 변수명만 다르지 변수와 같은 주소를 가지고 있습니다.
int main() {
int number = 5;
int &another_number = number;
another_number = 10;
std::cout << "number = " << number << std::endl;
std::cout << "another_number = " << another_number << std::endl;
std::cout << "number address = " << &number << std::endl;
std::cout << "another_number address = " << &another_number << std::endl;
}
기존 코드에서 주소를 출력하는 로직만 추가하였습니다. 출력 결과물은 다음과 같습니다.
number = 10
another_number = 10
number address = 0x7ff7b84e21dc
another_number address = 0x7ff7b84e21dc
number 와 another_number 는 모두 주소값이 0x7ff7b84e21dc 로 동일한 것을 확인할 수 있습니다. 즉 두 변수가 같은 주소에 든 값을 가리키고 있는 것입니다.
따라서 두 변수 중 하나의 값을 바꾸어도 모두 값이 바뀌는 결과가 발생합니다.
Pointer 는 다음과 같이 선언과 초기화를 따로할 수 있습니다.
int main() {
int a;
int *p; // pointer 선언
a = 10;
p = &a; // pointer 초기화
std::cout << *p << std::endl;
}
하지만 Reference 는 포인터와 달리 선언과 초기화를 반드시 동시에 해야 합니다.
int main() {
int a = 10;
int &r; // compile error
}
위 코드 처럼 선언만 할 경우 컴파일 에러가 발생합니다.
Reference 는 한번 특정 변수의 별칭이 될 경우 다른 변수의 별칭이 될 수 없습니다.
int main() {
int a = 10;
int b = 20;
int &ref = a;
ref = b;
std::cout << "ref = " << ref << std::endl;
}
ref 라는 Reference 를 변수 a 를 이용해 초기화 하였습니다. 이후 ref 에 b 를 대입하면 ref 는 b 의 별칭이 될까요? 각 변수들의 주소값을 출력해 보겠습니다.
int main() {
int a = 10;
int b = 20;
int &ref = a;
ref = b;
std::cout << "a = " << a << std::endl;
std::cout << "ref = " << ref << std::endl;
std::cout << "a's addr = " << &a << std::endl;
std::cout << "b's addr = " << &b << std::endl;
std::cout << "ref's addr = " << &ref << std::endl;
}
위 코드의 결과는 다음과 같습니다.
a = 20
ref = 20
a's addr = 0x7ff7bc6a11dc
b's addr = 0x7ff7bc6a11d8
ref's addr = 0x7ff7bc6a11dc
ref 는 a 와 같은 주소값을 가지는 것을 확인할 수 있습니다. 즉 한번 초기화 된 Reference에 다른 변수를 대입한다고 해서 Reference 가 다른 변수의 별칭이 되는 것이 아니라 처음 초기화 할때 사용한 변수와 레퍼런스의 값이 업데이트 될 뿐입니다.
반면 포인터는 한번 초기화 되어도 다른 변수의 주소값으로 바뀔 수 있습니다.
int main() {
int a = 10;
int b = 20;
int *p = &a;
std::cout << "a's addr = " << &a << std::endl;
std::cout << "p's value = " << p << std::endl;
p = &b;
std::cout << "b's addr = " << &b << std::endl;
std::cout << "p's value = " << p << std::endl;
}
위 코드를 실행하면 다음과 같은 결과가 출력됩니다.
a's addr = 0x7ff7ba0721dc
p's value = 0x7ff7ba0721dc
b's addr = 0x7ff7ba0721d8
p's value = 0x7ff7ba0721d8
64bit 시스템에서 포인터를 생성할 경우 메모리 상에서 포인터는8byte 의 크기를 차지합니다.
int main() {
int a = 10;
int *p = &a;
std::cout << "p's size = " << sizeof(p) << "byte" << std::endl;
}
p's size = 8byte
반면 Reference는 메모리상에 존재할 수 도 있고 존재하지 않을 수 있습니다. 단지 변수에 대한 별칭이기 때문에 컴파일러가 별칭을 원래 변수로 바꿔치기하면 Reference 는 메모리상에 존재할 필요가 없기 때문입니다. 하지만 매개변수로 Reference 를 받을 경우 메모리상에 존재하게 됩니다.
int& returnRef() {
int a = 10;
return a;
}
int main() {
int b = returnRef();
std::cout << b << std::endl;
}
레퍼런스를 반환하는 함수를 이용해 값을 받으면 다음과 같은 컴파일 오류가 발생합니다.
ReferenceMain.cpp:21:9: warning: reference to stack memory associated with local variable 'a' returned [-Wreturn-stack-address]
return a;
^
1 warning generated.
왜 이런 컴파일 오류가 발생하는 걸까요?

main() 함수에서 returnRef() 함수를 호출할 경우 콜스택은 위 그림과 같이 됩니다. 이때 지역변수 a 가 10으로 초기화 됩니다.

returnRef() 함수가 a 의 Reference 를 반환하면 call stack 내부에서 returnRef() 가 사라집니다. 따라서 a 라는 지역변수 또한 같이 사라지게 됩니다.
결국 반환된 Reference 는 참조할 변수가 사라지게 됩니다. 이를 Dangling Reference 라고 부릅니다.
Reference 의 특징중 하나는 선언과 초기화를 동시에 해야한다고 설명했습니다. Dangling Reference 는 이에 위배되기 때문에 컴파일 오류가 발생하게 되는 것입니다.
따라서 Dangling Reference 가 생기지 않도록 주의해야 합니다.
이와 유사한 경우는 한가지 더있습니다.
int returnRef() {
int a = 10;
return a;
}
int main() {
int &b = returnRef();
std::cout << b << std::endl;
}
ReferenceMain.cpp:26:7: error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
int &b = returnRef();
^ ~~~~~~~~~~~
1 error generated.
반환되는 값을 참조자로 받는 경우 또한 Dangling Reference 가 생성됩니다. 지역변수는 함수가 호출된 뒤 사라지기 때문이죠.
그런데 지역변수의 생명을 연장시키는 방법이 존재합니다.
int returnRef() {
int a = 10;
return a;
}
int main() {
const int &b = returnRef(); // const 사용
std::cout << b << std::endl;
}
const 키워드를 사용하면 반환되는 지역변수의 수명을 연장시킬 수 있습니다. 연장되는 기간은 레퍼런스가 사라질 때 까지 연장됩니다.
글을 깔끔하게 이해하기 쉽게 작성하신 거 같아요!! 잘 읽고 갑니다!!