wn42
코딩이랑 이것저것
wn42
전체 방문자
오늘
어제
  • 분류 전체보기 (113)
    • 프로그래머스 (23)
      • LV1 (11)
      • LV2 (1)
      • LV3 (3)
      • 연습 (8)
    • 딥러닝 공부 (0)
      • 머신러닝&딥러닝 이론 (0)
    • 임베디드 (17)
      • Adventure Design (1)
      • 센서기반모바일로봇 (5)
      • ROS (9)
      • Google Coral (2)
    • C++ (38)
      • C++ 기초 (34)
      • 자료구조 및 알고리즘 (4)
    • Python (14)
      • 기본 파이썬 문법 (6)
      • Python 기초 (8)
    • 빅데이터 (9)
      • 빅데이터 첫걸음 시작하기(국비지원) (5)
      • 빅데이터 공부 (4)
    • 알고리즘 공부 (2)
      • 기본 알고리즘 (2)
    • 전자공학 (10)
      • 반도체 공정 (3)
      • 무선데이터통신 (7)
      • 반도체공학 (0)
    • C# (0)
      • C# 기본 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 소멸자
  • 노드
  • 큐
  • K디지털크레딧
  • 빅데이터
  • 내일배움카드
  • 인스턴스
  • 상속
  • 데이터분석
  • ROS
  • google coral
  • 프로그래머스
  • 빅데이터 첫걸음 시작하기
  • 바이트디그리
  • 패스트캠퍼스
  • 스택
  • Python
  • 클래스
  • stl
  • 조건문
  • 데이터분석 인강
  • Queue
  • 딥러닝
  • c++
  • numpy
  • 반복문
  • 정렬
  • 스택/큐
  • 파이썬
  • 변수

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
wn42

코딩이랑 이것저것

C++/C++ 기초

[C++] 레퍼런스(참조자)

2022. 12. 30. 18:33

 레퍼런스 

C++에서 새로 등장한 기술로 특정 변수의 이름 대신 새로운 이름을 붙일 수 있다. 참조자라고 한다.

  • &(앰퍼샌드) 기호를 이용한다.
  • 레퍼런스를 이용하여 변수를 선언할 수 있으며 이를 참조형 변수라고 한다.
  • 하나의 객체가 다른 객체를 연결하는 변수이며, 대입된 변수의 값과 주소를 그대로 가진다.
  • 쉽게 말해서 다른 변수에 별명을 붙이는 변수라고 생각하자.

 

레퍼런스 변수 선언

  • 레퍼런스 변수는 다음과 같이 선언한다.
datatype &name = 변수명;

int &p = a;

 

Ex) 간단한 레퍼런스 변수 예제

#include <iostream>
using namespace std;

int main() {
	int a = 10;
	int& p = a;

	cout << "a = " << p << endl;

	p = 20;
	cout << "a = " << p << endl;
	return 0;
}
a = 10
a = 20
  • p는 a의 참조자이다.
  • int& p = a; 라는 코드에 의해 p는 a의 별명이라는 것을 컴파일러가 알게 된다.

 

 

레퍼런스 변수와 포인터의 차이점

  • 레퍼런스 변수와 포인터는 비슷한 기능을 수행하지만, 엄연히 다른 것이다.

 

1. 선언과 초기화

#include <iostream>
using namespace std;

int main() {
	int *ptr;
	int& ref;

	return 0;
}

  • 포인터는 선언만 하여도 상관없지만, 참조형 변수는 선언과 초기화를 동시에 진행하여야 한다.
  • 즉, 누군가의 별명인가를 정확히 명시해야 한다.

 

2. 값 변경 가능 여부

#include <iostream>
using namespace std;

int main() {
	int a = 10;
	int b = 20;

	cout << "a의 주소: " << &a << endl;
	cout << "b의 주소: " << &b << endl;
	cout << endl;

	int *ptr = &a;
	int& ref = a;

	cout << "ptr이 가리키는 주소: " << ptr << endl;
	cout << "ref이 가리키는 주소: " << &ref << endl;
	cout << endl;

	ptr = &b;
	ref = b;

	cout << "ptr이 가리키는 주소: " << ptr << endl;
	cout << "ref이 가리키는 주소: " << &ref << endl;
	cout << endl;

	cout << "ptr이 가리키는 값: " << *ptr << endl;
	cout << "ref이 가리키는 값: " << ref << endl;
	cout << endl;

	cout << "a의 값: " << a << endl;
	cout << "b의 값: " << b << endl;
	return 0;
}
a의 주소: 00BEFA8C
b의 주소: 00BEFA80

ptr이 가리키는 주소: 00BEFA8C
ref이 가리키는 주소: 00BEFA8C

ptr이 가리키는 주소: 00BEFA80
ref이 가리키는 주소: 00BEFA8C

ptr이 가리키는 값: 20
ref이 가리키는 값: 20

a의 값: 20
b의 값: 20
  • 포인터 ptr과 참조자 ref가 가리키는 변수를 a에서 b로 변경하였다.
  • 포인터 ptr은 성공적으로 b를 가리키도록 변경되었지만, 참조자 ref는 그대로 a의 값과 주소를 가리키고 있다.
  • 초기화와 동시에 참조자 ref는 변수 a의 별명으로 고정되었고, ref = b; 라는 코드는 a=b;와 동치이다. 따라서 ref가 b를 가리키는 코드가 아닌, a의 값이 10에서 20으로 변하는 코드이다.
  • 즉, 포인터는 값을 변경할 수 있지만 참조자는 한 번 초기화를 진행하면 값을 변경할 수 없다.

 

3. 레퍼런스는 메모리 공간이 할당되지 않을 수 있다.

  • 포인터는 선언과 동시에 메모리 상에서 공간이 할당되는 변수이다.
  • 반면, 레퍼런스 변수는 별명으로만 쓰이는 경우에는 별도의 메모리 공간을 할당하지 않는다. 기존 변수의 메모리 공간을 그대로 사용한다.
  • 그러나 레퍼런스가 항상 메모리 공간을 할당받지 않는 것은 아니다. 필요에 따라 메모리 공간을 할당한다.
  • 자세한 내용은 아래 사이트를 참고한다.
 

Reference declaration - cppreference.com

Declares a named variable as a reference, that is, an alias to an already-existing object or function. [edit] Syntax A reference variable declaration is any simple declaration whose declarator has the form & attr(optional) declarator (1) && attr(optional)

en.cppreference.com

 

 

레퍼런스 변수를 인자로 넘기기

  • 레퍼런스 변수 또한 함수의 인자로 넘길 수 있다.
  • call-by-reference로 동작한다. 포인터로 넘기는 것보다 더욱 깔끔한 구현이 가능하다.

 

Ex) 간단한 레퍼런스 인자 넘기기 예제

#include <iostream>
using namespace std;

int plus_ten(int&);

int main() {
	int a = 10;
	cout << "a의 값(함수 호출 전): " << a << endl;

	plus_ten(a);
	cout << "a의 값(함수 호출 후): " << a << endl;
	return 0;
}

int plus_ten(int& n) { // 레퍼런스를 인자로 넘기기
	n += 10;
	return 0;
}
a의 값(함수 호출 전): 10
a의 값(함수 호출 후): 20
  • 참조자를 이용하여 간단하게 call-by-reference를 구현할 수 있다.

 

 

상수 참조하기

  • 기본적으로 레퍼런스로 상수 리터럴을 참조하는 것은 불가능하다.
  • 대신 참조자 또한 상수 처리한다면 상수 리터럴도 참조할 수 있다.

 

Ex) const 참조자

#include <iostream>
using namespace std;

int main() {
	const int& ref = 10;
	cout << "ref의 값: " << ref << endl;

	int a = ref;    // a = 10; 과 동치
	cout << "a의 값: "<< a << endl;

	return 0;
}
ref의 값: 10
a의 값: 10
  • 상수 참조자를 선언하면 상수 리터럴을 참조할 수 있다.

 

 

레퍼런스 배열

  • 결론부터 말하자면 레퍼런스 배열은 illegal(불법)이다.
  • 레퍼런스 배열은 구현할 수 없다는 것이 C++ 규정이다. ▶ 레퍼런스의 레퍼런스, 레퍼런스의 포인터 또한 불가능
  • 배열의 이름은 주소값으로 변환할 수 있어야 한다. ▶ 배열 arr이 있을 때 arr은 *(arr + 0)와 동일하다. 배열의 이름이 주소값으로 변환될 수 있으며, 각 배열의 원소들이 메모리 공간을 차지하고 있음을 의미한다.
  • 그러나 레퍼런스는 앞서 말했듯이 대부분의 경우 메모리 공간을 할당하지 않는다. 따라서 레퍼런스 배열을 정의한다는 것 자체가 모순이다.

 

 

배열 레퍼런스

  • 배열 레퍼런스는 가능하다.
  • 구현할 시 배열의 크기를 명시해야 한다.

 

Ex) 배열 레퍼런스 구현

#include <iostream>
using namespace std;

int main() {
	int arr[2][2] = { {1, 2}, {3, 4} };
	int(&ref)[2][2] = arr;

	ref[0][0] = 10;
	ref[0][1] = 20;
	ref[1][0] = 30;
	ref[1][1] = 40;

	cout << "arr[0][0]의 값: " << arr[0][0] << endl;
	cout << "arr[0][1]의 값: " << arr[0][1] << endl;
	cout << "arr[1][0]의 값: " << arr[1][0] << endl;
	cout << "arr[1][1]의 값: " << arr[1][1] << endl;
	return 0;
}
arr[0][0]의 값: 10
arr[0][1]의 값: 20
arr[1][0]의 값: 30
arr[1][1]의 값: 40
  • 배열 레퍼런스는 꼭 배열의 크기를 명시해야 함에 유의하자.

 

 

레퍼런스 리턴 함수

  • 레퍼런스는 반환할 수 있다.
  • 레퍼런스 리턴은 크기가 매우 큰 구조체를 처리할 때 유용하게 사용된다. 해당 구조체 전체를 복사할 필요없이 주소값만 복사하면 되기 때문이다. 포인터의 장점과 동일하다. 
  • 다만, 지역변수의 레퍼런스를 리턴하는 경우는 없어야 한다.

 

지역변수의 레퍼런스 리턴

#include <iostream>
using namespace std;

int &ref();

int main() {
	int b = 0;
	cout << "b의 값(함수 호출 전): " << b << endl;
	b = ref();
	cout << "b의 값(함수 호출 후): "<< b << endl;
	return 0;
}

int &ref() {
	int a = 10;
	return a;
}
b의 값(함수 호출 전): 0
b의 값(함수 호출 후): 10
  • 좀 전에 지역변수의 레퍼런스를 반환하지 말라 했는데 값은 제대로 나왔다.
  • 컴파일 오류는 발생하지 않았지만 다음과 같은 경고가 나타난다.

 

댕글링 레퍼런스(Dangling Reference)

  • 코드를 다시 간단하게 해석하자면 다음과 같다.
int &ref = a;    // a의 값은 10
int b = ref;
  • 참조 변수 ref는 a의 별명이다. 그리고 b에는 ref를 대입한다.
  • 위의 경우에서는 b가 a 값을 제대로 받기는 했다.
  • 하지만 ref의 참조 대상인 a가 메모리 상에서 사라졌다. 이때 변수 a가 할당받았던 메모리는 garbage memory이다. (다시 반환되어야 할 메모리)
  • 이와 같은 상황에서 레퍼런스는 참조할 대상 없이 존재하는 상황이 되어 버리며, 이를 댕글링 레퍼런스라고 한다. (달랑달랑 거리는 상태를 의미)
  • 다음과 같은 상황이 있다고 해보자.
#include <iostream>
using namespace std;

int &ref();

int main() {
	int b = 0;
	cout << "b의 값(함수 호출 전): " << b << endl;
	
	// 짧은 순간에 메모리가 재활용 된다는 가정
	int *garbage;
	garbage = &ref();
	*garbage = 40;
	
	// b는 재활용 과정에 의해 이상한 값을 받게 됨
	b = *garbage;
	cout << "b의 값(함수 호출 후): "<< b << endl;
	return 0;
}

int &ref() {
	int a = 10;
	return a;
}
b의 값(함수 호출 전): 0
b의 값(함수 호출 후): 40
  • 위처럼 짧은 순간에 재할당되어야 할 가비지 메모리를 재활용하여 값이 변경되는 상황이 발생할 수 있다.
  • 따라서 지역변수의 레퍼런스를 반환하는 일은 매우 큰 오류를 야기할 수 있다.
 

What is dangling reference?

Answer (1 of 2): A dangling reference is a reference to an object that no longer exists. Dangling reference arise during object destruction, when an object that has an incoming reference is deleted or deallocated, without modifying the value of the pointer

www.quora.com

(이를 참고하였음. 틀릴 수 있으므로 잘못된 내용은 댓글 부탁드립니다.)

 

 

외부 변수의 레퍼런스 리턴

#include <iostream>
using namespace std;

int &ref(int&);

int main() {
	int b = 0;
	int c = 3;
	cout << "b의 값(함수 호출 전): " << b << endl;
	b = ref(c);
	cout << "b의 값(함수 호출 후): " << b << endl;
	return 0;
}

int &ref(int &a) {
	a = 10;
	return a;
}
b의 값(함수 호출 전): 0
b의 값(함수 호출 후): 10
  • 이전과 동일하게 인자로 받은 레퍼런스를 그대로 반환했음에도 컴파일 에러와 warning 구문이 발생하지 않는다.
  • 이번에는 a가 외부 변수인 c를 참조하였기 때문에, 함수가 종료되더라도 ref가 반환한 참조자는 살아있는 변수 c를 계속해서 참조하게 된다.
  • 위 코드는 단순히 함수 호출과 함께 바뀐 c의 값 10을 b에 대입하는 것과 동일하다.

 

참조자가 아닌 값을 리턴하는 함수를 참조자로 받기

  • 이 경우 또한, 댕글링 레퍼런스가 되어버리며 아예 컴파일도 되지 않는다.

 

Ex) 참조자가 아닌 값을 리턴하는 함수를 참조자로 받기

#include <iostream>
using namespace std;

int ref();

int main() {
	int& b = ref();
	cout << "b = " << b << endl;
	return 0;
}

int ref() {
	int a = 10;
	return a;
}

  • 참조자가 상수가 아니라면 lvalue 초기값을 가져야 한다.
  • lvalue란 예를 들어 a = 10이라는 대입식에서 대입연산자 '=' 기호의 왼쪽에 올 수 있는 값이다. 해당 식이 실행완료되어도 사라지지 않는 값이며 이름을 갖는 변수를 말한다. 반면 rvalue는 대입연산자 오른쪽에 해당하는 값으로, 대입식이 실행된 후 사라지는 임시 변수를 말한다.
  • 즉 해당 오류는 사라지지 않는 값(지역을 벗어나면 메모리가 사라지는 지역변수가 아닌)을 초기값으로 가져야 함을 의미한다. ▶ b가 댕글링 레퍼런스가 되어버리기 때문
  • 이 오류는 다음과 같이 해결할 수 있다.

 

Ex) const 참조자 이용

#include <iostream>
using namespace std;

int ref();

int main() {
	const int& b = ref();
	cout << "b = " << b << endl;
	return 0;
}

int ref() {
	int a = 10;
	return a;
}
b = 10
  • const 키워드를 이용하여 상수 레퍼런스로 정의하였다.
  • 이 경우에는 상수 처리를 하였기 때문에 리턴값이 그대로 메모리 상에 유지되며, 해당 상수 레퍼런스가 사라지지 않는 이상 계속 값이 유지된다.
  • 그냥 이것저것 따지지 말고 지역변수의 레퍼런스를 리턴하지 말자.
    'C++/C++ 기초' 카테고리의 다른 글
    • [C++] 객체 지향 프로그래밍
    • [C++] 동적 메모리 할당
    • [C++] 매크로 / 인라인
    • [C++] void 타입 / main 함수
    wn42
    wn42
    코딩이랑 이것저것 하는 블로그

    티스토리툴바