F.R.I.D.A.Y.

C++ 레퍼런스 타입 본문

DEV/C C++

C++ 레퍼런스 타입

F.R.I.D.A.Y. 2021. 10. 7. 18:30
반응형

 C++에서 사용하는 레퍼런스 타입


함수로 값 전달하기

 C에서 함수로 값을 전달하는 방법에는 크게 두 가지 방법이 있습니다.

  • 값으로 전달(Call by Value)
  • 주소로 전달(Call by Address)

 사실, 주소로 전달하는 방식 또한, 결국은 값에 의한 전달과 동일한 개념이지만 일반적으로 값으로 전달했을 때의 단점을 상쇄할 수 있기 때문에 별도의 방식으로 보고 있죠.

 

 C++에서는 하나의 타입이 추가됩니다.

 

레퍼런스 타입

 C++에는 레퍼런스 타입이라는 불리는 타입이 추가되었습니다. 아래처럼 사용할 수 있습니다.

int a = 5;
int& ref = a;

ref = 7; // a도 7이 됨

 레퍼런스 타입으로 변수를 선언하면, 대입한 변수의 공간을 함께 공유합니다.

 

 

CallbyAddress와의 차이

 레퍼런스로 변수를 이용하는 것은 주소를 이용하는 포인터와는 메모리에서부터 그 의미가 다릅니다. 주소를 이용하는 포인터 타입은 자체적인 메모리 공간이 있습니다.

 

표를 보게되면, 변수 a와 포인터 변수 ptr 모두 스스로의 메모리 공간을 가지고 있습니다.

더보기

# 변수의 탄생사

 실제 프로그램의 작동 방식은 아래와 같습니다.

명령 주소, 값

 이때, 프로그래머가 매번 주소를 작성해야하는 문제로 인해 변수가 발생했습니다. 더 자세한 이야기는 다른 포스트에서 이어갑니다.

 

// 생성시 추가됩니다.

 

 그러나 레퍼런스 타입은 조금 다릅니다.

 레퍼런스 타입은 자신만의 공간이 존재하지 않습니다. 그러나 독립적으로 존재할 수도 없습니다. 단순 레퍼런스 변수 선언만 할 수는 없는 것입니다.

int a;

int& ref; // 오류
ref = a;

 

 즉 레퍼런스 타입은 본인의 공간이 존재하지 않은, 이미 이름이 있는 메모리 공간을 가리키는 새로운 별명같은 개념이라 생각하면 됩니다.

 

포인터 변수와의 차이

 위에서 이미지로 보았다면, 코드로 한번 다시 보겠습니다.

 

 포인터 변수는 기본적으로 본인만의 공간을 가지고 있습니다. 레퍼런스는 이론상 자신만의 공간을 가지지 않습니다. 따라서 참조되는 공간의 성격이 중요합니다.

 

 포인터의 경우, 참조하는 공간이 사라지더라도 본인의 공간이 있기 때문에 문제가 되지 않습니다. 다음 코드를 보겠습니다.

int *p;
{
	int temp;
    p = &temp;
}

 위 코드에서 p는 포인터 변수로서, 내부 스코프에 존재하는 temp 변수를 가리키도록 구성되어 있습니다. temp 변수는 스코프가 끝나면 함께 사라지겠죠. 따라서 액세스 문제가 생깁니다. 그러나 포인터 p는 자신만의 공간이 있으므로 큰 문제가 되지 않습니다.

 포인터의 경우 해당 가리키는 공간으로 바로 참조를 하는 것이 아니라, 포인터 자신이 가지고 있는 값을 주로소 인식하는 한 단계를 거치는 과정이 있기 때문에 "간접 참조"의 성격을 띄게 됩니다.

 

 그러나 레퍼런스로 넘어오게 되면, 상황은 달라집니다. 레퍼런스 타입은 본인만의 공간이 없이, 다른 공간에 붙어 사는 성격입니다. 코드로 보겠습니다.

int& ref; // error
{
	int temp;
    ref = temp;
}

 애초에 레퍼런스의 단독선언에서부터 말이 되지 않습니다. ref 변수는 레퍼런스로서, 본인의 공간이 존재하지 않습니다. 따라서 공간을 공유할 다른 변수를 넣어주어야하는데, 선언 시점에 그 과정이 이루어져야 합니다. 포인터는 자기 공간을 기반으로 다른 공간을 참조하는 "간접 참조"이므로 상관이 없으나, 직접 참조 방식인 레퍼런스 타입은 문법상 선언과 참조 대상의 공간을 함께 넘겨주어야 하므로 포인터와 전혀 다른 방식으로 이루어진 셈입니다.

 

 

실제 코드에서의 레퍼런스 타입

 실제 환경에서 컴파일을 진행할 때, 레퍼런스가 어떻게 작동하는지 알아봅시다.

int main() {
	int a;
	int& ref = a;

	a = 6;
	ref = 7;

	return 0;
}

 이 코드를 Visual Studio 2019[#version v16.11.3\nSDK10.0\nC++14]에서 컴파일해 어셈블리어로 확인하면 아래와 같습니다.[# 여기에서 컴파일한 코드는 최적화가 전혀 되지 않은 코드를 기준으로 합니다.]

; ref = a
lea eax, [a]
mov dword ptr [ref], eax

; a = 6
mov dword ptr [a], 6

; ref = 7
mov eax, dword ptr [ref]
mov dword ptr [eax], 7

컴파일 결과를 보면 위와 같이 포인터 타입으로 구성을 하고 있습니다. 이런 부분은 컴파일러 재량인 부분입니다. 실제로, C++ 공식 문서에서는 레퍼런스 타입의 정확한 구현 명세가 없습니다.

References are not objects; they do not necessarily occupy storage, although the compiler may allocate storage if it is necessary to implement the desired semantics (e.g. a non-static data member of reference type usually increases the size of the class by the amount necessary to store a memory address).

레퍼런스는 객체가 아닙니다. 따라서 공간이 필요하지 않지만, 논리적으로 구현하는데 필요하다면 컴파일러는 공간을 할당할 수 있습니다. (예를 들어서 static 데이터가 아닌 레퍼런스 타입은 일반적으로 메모리 주소를 저장하는데 필요한 만큼의 클래스 크기가 증가합니다.)[# 발의역이기 때문에 정확하지 않습니다. 그 의미를 이해할 정도이니 정확한건 원문이 기준입니다]
https://en.cppreference.com/w/cpp/language/reference

 이러한 구현은 GCC에서도 동일합니다.

테스트 사이트

 

Compiler Explorer

 

godbolt.org

 

 

그래서?

 우리는 레퍼런스 타입의 이론적인 부분과 실제 코드에서의 구현을 함께 보았습니다. 이론적인 부분과 실제 코드상의 부분이 서로 차이가 나는 부분에서 무엇에 맞추어야 하는지 혼동이 올 수 있습니다. 그러나 우리가 어셈블리어로 코드를 작성할 것이 아니라면, 레퍼런스의 실제 코드 구현은 컴파일러에 맡기게 됩니다. 즉, 우리는 이론적인 부분에서 접근하고 사고해야하는 것이 맞습니다. 어셈블리어 부분까지 고려하는 것은 그 이후의 일입니다.

 

 


참고

 

참조자도 메모리 공간에 할당되나요?

흔히 C++의 참조자를 설명할 때 참조 대상에 붙이는 새로운 이름이라고 설명하는데 인터넷에 보니까 익명으로 가려진 변수가 할당된다는 말도 있어서 조금 헷갈리네요. 참조자 선언 시 새로운

hashcode.co.kr

 

# index

728x90
반응형

'DEV > C C++' 카테고리의 다른 글

exe 파일 아이콘 변경  (1) 2022.07.12
함수 포인터 예시  (0) 2021.12.19
printf 이진수 출력(사용자 지정 서식지정자 구현)  (0) 2021.09.14
다중 모니터 위치 파악하기  (0) 2021.07.16
PC 카카오톡 AD 제거  (0) 2021.06.25
Comments