C언어와 C++의 차이점 중 하나로 종종 언급되는 참조자. 하지만 C++은 정확하게 말하자면 C언어 또한 포함하는 개념이다. 그래도 C언어와 C언어를 제외한 C++를 비교하면 올바른 표현이다.
참조자(reference)
참조자를 설명하기 전에 우리가 아주 익순한 변수(variable)를 먼저 살펴보자. 변수란 무엇인가? 너무 익숙한 개념은 의외로 설명하기 난감할 때가 있다. 변수도 이런 예 중에 하나이다. 프로그래밍에서 변수는 할당된 메모리 공간에 붙여진 이름으로 정의할 수 있다. 그 이름을 통해서 해당 메모리에 접근할 수 있다.
매우 단순한 코드이다. 이를 변수의 정의를 참고하여 설명해보자. 10이라는 데이터를 저장하기 위해서는 메모리를 할당해야 한다. 할당된 메모리에 10을 저장하고, 이 메모리에 접근하기 위한 이름이 필요하다. 이 이름에 바로 변수이다. 그리고 우리는 늘 그러하듯이 a를 이용해서 그 메모리 공간에 접근할 수 있다.
그러면 위의 코드에서 10이 할당된 메모리 공간에 접근하기 위한 이름(변수)이 하나 더 추가할 수는 없을까? a라는 변수가 아닌 b라는 변수도 10이 저장된 메모리 공간에 접근할 수는 없을까? 즉 할당된 하나의 메모리 공간에 둘 이상의 이름을 부여할 수 없는가? 이를 가능하게 하는 것이 바로 참조자(reference)이다.
참조자 선언과 사용
참조자 선언에 대해 살펴보자.
참조자를 선언하고 정의한 부분을 살펴보면 익숙한 연산자 &가 등장한다. & 연산자는 포인터(pointer)를 공부한 사람이면 누구나 알고 있을 것이다. & 연산자는 변수의 주소값을 반환한다. 변수의 주소값을 반환하려면 할당된 메모리 공간이 이미 있어야 한다. 하지만 ref라는 변수를 대상으로 & 연산자를 사용하였다는 점에 주목하자. ref는 메모리 공간에 무엇도 할당되지 않은 상태인데 어떻게 ref를 대상으로 주소값 반환을 할 수 있겠는가? 상식적으로 맞지 않다. 그렇다면 여기서 &는 무엇을 의미하는가? 바로 참조자를 의미한다. 새로 선언되는 변수의 이름 앞에 등장하는 & 연산자는 참조자를 의미한다. 정리하면 다음과 같다. 이미 선언된 변수의 앞에 등장하는 & 연산자가 오면 주소값을 반환하라는 명령이고, 새로 선언된 변수의 앞에 등장하는 & 연산자는 참조자 선언을 의미한다. 이를 코드로 살펴보자.
num은 이미 선언된 변수이다. 이 때 & 연산자는 주소값을 반환하는 연산자이고, 포인터 변수 ptr은 그 주소값을 저장한다. 반면에 ref는 새로 선언된 변수이고, 이 때 & 연산자는 참조자를 의미하는 선언이다. num이라는 변수가 할당된 메모리 공간을 가리키는 또 다른 이름, 즉 또 다른 변수가 된다. 이 정도면 참조자 선언에 대한 이해는 충분할 것으로 생각한다.
위 코드의 실행결과는 어떻게 될까? 메모리 어느 공간에 10이 할당되어 있고, num1 변수로 가리키고 있다. num1 변수가 가리키는 공간에 대해 num2라는 참조자를 선언했다. 즉 num1과 num2가 동일한 메모리 공간을 가리키고 있다. num2에 20을 저장하면 num1, num2가 가리키는 동일한 메모리 공간이 10으로 변경된다. 결국 num1, num2가 가리키는 메모리 공간에 저장된 값이 같고, 결국 num1, num2에 저장된 주소값 또한 동일하다.
참조자는 이미 선언된 변수를 대상으로 사용할 수 있다고 하였다. 위 코드에서 ref1은 변수가 아닌 상수를 대상으로 참조자를 선언했기 때문에 에러이다. ref2는 참조하는 대상이 없기 때문에 에러이다. ref3는 NULL값을 참조하기 때문에 에러이다.
참조자와 함수 사용
위 코드의 실행결과는?
위 코드의 실행결과는?
이 두 코드의 실행결과를 이해했다면 참조자에 대한 어느 정도 이해했다고 볼 수 있다.
다음 코드의 문제점은 무엇인가? 이는 지역변수와 포인터에 대한 이해와 유사한 방식으로 생각하면 답을 찾을 수 있다. num이라는 지역변수는 이 함수를 벗어나면 사라지는 메모리 공간이다. stack에 저장되어 있는 공간이다. 이 공간에 대해서 return value로 참조자를 선언해두었다. num에 대한 참조자가 존재하는데 stack에서 num이 사라지면 이를 가리키는 참조자는 어떻게 될까? 이는 댕글링 포인터(dangling pointer) 문제와 매우 유사하다. 참고로 위 코드는 warning만 발생시킬 뿐 error를 발생시키는 코드가 아니므로 더욱 주의해야 한다.
Const 참조자
위의 코드는 컴파일 에러를 발생시킨다. 어느 부분이 잘못되었는가? num은 const로 상수화되어있다. 변경 불가능한 상태이다. 이 변수에 대한 참조자 ref가 선언되어 있다. ref를 통하여 num이 저장된 메모리에 접근할 수 있고, 이는 잠재적으로 num에 저장된 데이터를 변경할 수 있다는 의미이다. 이는 const를 선언한 의미에 부합하지 않는 내용이다. 그래서 컴파일러는 이와 같은 코드를 에러로 처리한다.
위의 코드는 에러를 발생시키지 않는다. 참조자 역시 const로 선언하였기 때문이다. 상수화된 ref는 num을 변경시킬 능력이 없으므로 컴파일러는 에러 처리하지 않고 넘어간다.
지금까지의 예시를 생각하며 다음 코드를 살펴보자.
이 코드는 에러를 발생시킬까? 앞에서 참조자는 변수만 참조할 수 있다고 하였다. 그렇다면 이 코드는 분명 에러이다. 하지만 이 코드는 에러없이 실행된다. 어떻게 이해할 수 있을까? 방금 전에 참조자를 const로 선언하면 상수를 참조할 수 있음을 확인하였다. 이 부분 역시 그렇게 이해할 수 있지 않을까? 먼저 상수 10이 사용되기 위해서는 메모리 공간에 존재해야 한다. 메모리 공간에 존재하는 공간을 const 참조자로 참조하기 위해서는 변수가 필요하다. 이때 사용되는 변수가 바로 '임시변수'이다. 10과 같은 상수를 이름없는 상수, 즉 리터럴 상수(literal constant)이다. 이 리터럴 상수에 대한 참조자를 만들기 위해서 사용되는 변수가 바로 임시변수이다. 다시 말해 ref는 리터럴 상수 10에 대한 임시변수를 참조하고 있다.
이런 자세한 설명을 한 이유는 무엇일까? 이는 const 참조자를 이용한 함수 호출이 자주 사용되기 때문이다.
우리는 리터럴 상수에 대한 참조는 임시변수를 통해 가능하다는 사실을 알고 있다. 함수의 인자로 const 참조자가 선언되어 있으면 어떻게 될까? 리터럴 상수를 참조하기 위해 임시변수를 생성하고, 이에 대한 참조자가 만들어 질 것이다. 그 참조자가 바로 num1, num2이다. 그리고 num1 + num2를 한 결과를 return value에 복사하여 전달한다.
만약 const 참조자가 리터럴 상수를 참조할 수 없다면 어떻게 되겠는가?
우리는 위의 코드처럼 상수를 변수로 받아서 함수를 호출할 수밖에 없다. 매우 번거롭고 불편하다. 이를 편하게 할 수 있게 하기 위해 const 참조자는 리터럴 상수를 참조할 수 있는 것이다. const 참조자가 나오면 이 사실을 한번씩 떠올려보면 좋겠다.
좋은 글 감사합니다^^
답글삭제도움이 되었다니 다행이네요^^
삭제자세한 설명 감사합니다~~
답글삭제질문이 있습니다!!
답글삭제임시변수에 대한질문인데 마지막에서 두번재 예제에서
int Adder( const int &num1, const int &num2 )
{
return num1 + num2;
}
std::cout << Adder( 1, 2 ) << std::endl;
여기서 해당 함수의 인자 1, 2 를 매개변수가 const 참조자를 사용했음으로
풀이를 한다면 const int &num1과 &num2는 1과 2의 주소값을 참조하는것이 아니라
명확하게 말하면 1과 2가 할당되어있는 임시변수의 주소값을 참조한다는 의미가 정확한 의미인가요??