C++ Reference

joonan·2024년 11월 17일

cpp

목록 보기
1/1

C++ 의 Reference


기존 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의 원리


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

numberanother_number 는 모두 주소값이 0x7ff7b84e21dc 로 동일한 것을 확인할 수 있습니다. 즉 두 변수가 같은 주소에 든 값을 가리키고 있는 것입니다.

따라서 두 변수 중 하나의 값을 바꾸어도 모두 값이 바뀌는 결과가 발생합니다.

Reference 와 Pointer 의 차이점


1. Reference 는 선언과 초기화를 동시에 해야 한다.

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

}

위 코드 처럼 선언만 할 경우 컴파일 에러가 발생합니다.

2. 한번 별칭이 되면 절대 다른 이의 별칭이 될 수 없다.

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

3. 메모리상 존재 유무

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 를 받을 경우 메모리상에 존재하게 됩니다.

Reference 를 return 하는 함수


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 키워드를 사용하면 반환되는 지역변수의 수명을 연장시킬 수 있습니다. 연장되는 기간은 레퍼런스가 사라질 때 까지 연장됩니다.

Reference

profile
끄적 끄적

1개의 댓글

comment-user-thumbnail
2024년 11월 17일

글을 깔끔하게 이해하기 쉽게 작성하신 거 같아요!! 잘 읽고 갑니다!!

답글 달기