상세 컨텐츠

본문 제목

C++ 참조자(reference)

C++/C++98

by deulee 2023. 8. 3. 15:11

본문

우선 참조자란 무엇일까?

 

참조자(Reference)는 다른 변수 또는 객체에 대한 별칭(alias)을 만들어주는 개념이다. 즉, 참조자를 사용하면 변수에 별칭을 부여하여, 원본 변수에 직접적인 접근 없이도 변수를 조작할 수도 있다는 개념이다. 이는 C++에서 포인터와 비슷한 개념으로 생각할 수 있지만, 사용면에서 몇 가지 중요한 차이점이 있다.

 

1. 참조자의 선언

int num = 5;
int& ref_num = num;

우선 변수 타입 앞에 '&' 기호를 붙이는 것으로 선언할 수 있다.

 

2. 참조자의 별명

int c = 5;
int& ref_c = c;

ref_c = 6; // c도 6으로 바뀜.

참조자는 변수의 별명(alias)이기 때문에 참조자를 통해 원본 변수를 조작하면 실제로 원본 변수의 값이 변경된다. 즉, 참조자는 원본 변숭와 같은 메모리 공간을 공유한다.

printf("%p %p\n", &c, &ref_c);

출력결과

둘의 메모리 주소가 같은 것을 알 수 있다.

3. NULL 값 할당 불가능

int& ref = NULL; // 불가능
int& ret = c;
ret = b; // 불가능

참조자는 선언과 동시에 누군가를 참조해야 하는 규칙때문에, 초기화 이후 다른 변수를 참조할 수 없다. 즉, 참조자를 NULL로 초기화하거나 다른 변수로 변경하는 것이 불가능하다.

 

이러한 특성을 가지고 있는 참조자는 보통 매개변수로 넘길 때 많이 사용한다. 이렇게 함으로써 객체와 같은 커다란 인스턴스를 재할당할 필요가 없기 때문이다.

void hello(int& a)
{
	a = 4;
}

int main(void)
{
	int a;
 	hello(a);
	return 0;
}

 

참조자는 과연 항상 메모리 상에 존재하지 않을까?

결론부터 말하자면 메모리를 할당받는다는 것이다.

 

즉, call by reference를 하게 되면 메모리가 할당되는데, 실제로 할당되는지 확인해보도록 하자.

 

#include <iostream>

void func(int* d)
{
	std::cout << *d << std::endl;
}

void func1(int& d)
{
	
}

int main(void)
{
	int c = 5;
	func(&c);
	func1(c);
	return 0;
}

아래는 해당 코드를 어셈블리로 나타낸것을 볼 수 있다.

call by address나 call by reference나 해당 함수에서 사용할 메모리 만큼을 스택에 저장한 것을 알 수 있다.

 

사실 컴파일러 입장에서는 매개변수로 인자가 전달되는 방식은 call by value와 call by address(call by reference)로 나뉘게 된다. 즉, 컴파일러 입장에서 참조자나 포인터나 동일한 것으로 받아들인다는 것이다.

 

이렇게 전달받은 주소값은 호출되는 함수의 스택 메모리에 저장이 되어 사용된다. 즉, 참조자로 매개변수를 전달받는다고 하더라도, 전달받는 a의 주소값이 fucn() 함수의 스택 메모리에 저장이 되어서 사용된다는 것이다.

 

물론 주소를 찍는다면 주소가 동일하게 나오지만 메모리로 보면 참조자에 의해서 할당된 메모리 공간을 확인할 수 있다.

 

참조자가 안되는 것

사실 참조자가 안되는 부분은 위의 내용 말고 더 있다.

 

1. 상수에 대한 참조자는 불가능하다.

 

상수는 리터럴 값인데, 메모리 측면에서 살펴보면 리터럴 값은 text segment에 정의가 되며 이 메모리 공간은 읽기만 가능하다.

 

하지만 참조자가 사용하다면 참조자를 이용해서 변경을 시도할 수 있기 때문에 상수를 참조하는 것은 금지가 된다.

 

하지만 const 키워드와 함께 사용한다면 가능하다.

const int& c = 5;

 

2. 참조자의 배열 / 배열의 참조자

 

결론부터 말하자면 참조자의 배열은 만들 수 없다.

int main(void)
{
	int a = 1;
    int b = 2;
    
    int& refArr[2] = { a, b }; // 에러
    return 0;
}

 

배열의 이름은 첫 번째 원소의 주소값으로 변환이 되어야 한다. 즉, 배열은 무조건 주소가 존재해야 하는데, 참조자의 경우에는 메모리 상에 존재하지 않을 수 있기 참조자들의 배열을 정의할 수는 없다. 

 

하지만, 배열의 참조자는 가능하다.

int main(void)
{
	int arr[3] = {1, 2, 3};
	int (&refArr)[3] = arr;
    
	return 0;
}

요런식으로 가능하다.

 

이 외에도 참조자를 사용하면 안되는 경우가 종종 있지만 이는 나중에 하나씩 나올것이다. 지금은 참조자가 무엇이고 참조자를 어떻게 사용하는지 알기만 하는게 중요하다.

 

 

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

C++ 생성자, 파괴자, 복사 생성자, 복사 대입 연산자  (0) 2023.08.03
C++ 함수 오버로딩  (0) 2023.08.03
C++ 클래스  (0) 2023.08.03
OOP란?  (0) 2023.08.03
C++에서의 구조체  (0) 2023.08.03

관련글 더보기