Call by Value와 Call by Reference
Programming/C/C++ 2010/06/01 23:44
1. value와 reference는 무슨 뜻인가?
먼저, value와 reference를 네이버 영어사전으로 검색해 보겠습니다.
value는 '가치'라는 뜻이 먼저 나오고, 마지막 부분에 "수학용어"로서 '값'이라는 뜻이 있다고 나옵니다.
reference는 '참조'라는 뜻이 많이 쓰이는 편이어서 통합검색부분에 바로 나오는 것 같습니다.
우리가 "지뢰를 모두 안전하게 찾으면 이기는 게임"의 특성을 잘 살려 "지뢰찾기"라고 이름을 짓듯이, 컴퓨터 용어를 지을 때 역시 관련된 특성을 영어로 잘 살리려 할 것입니다. 그래서 Call by Value와 Call by Reference를 알아보기 전에 핵심단어(value와 reference)의 사전적 의미를 알아본 것입니다.
Call by Value는 영어 그대로 값(Value)에 의한(by) 호출(Call)이고, Call by Reference역시 영어 그대로 참조(Reference)에 의한(by) 호출(Call)입니다.
2. Call by Value
Call by Value는 우리말로 "값에 의한 호출"이라는 의미로, 어떤 함수를 호출할 때 전달인자(Parameter)로 데이터 자체를 전달하는 호출 방법입니다.
2.1 스왑함수(Swap Function)의 구현 1
어떤 변수의 값을 서로 바꾸는 작업을 "스왑 한다."라고 합니다. 이는 프로그램을 만들 때 빈번히 일어나는 경우로서, 다음과 같은 작업을 거쳐야 합니다.
사람이야 머리로 두 자료(자료1, 자료2)의 위치를 바꿀 수 있지만, 컴퓨터는 바보(?)라서 임시저장공간이 반드시 있어야 합니다. 위 그림에서 숫자가 매겨진 것이 작업의 순서입니다. 값을 바꿀 자료 두 개(자료1, 자료2)에 대하여
1. "자료1"을 임시저장공간에 복사합니다.
2. "자료2"를 "자료1"에 복사합니다.
3. 임시저장공간에 저장된 "자료1"과 동일한 내용의 자료를 "자료2"에 복사합니다.
4. 필요에 따라 임시저장공간을 제거합니다.
와 같은 작업이 이루어지면 두 자료의 위치가 바뀝니다. 임시저장공간이 있어야 하는 까닭은 2번 과정을 통해 자료1의 내용이 손실되기 때문입니다.
이 내용을 바탕으로 스왑함수를 Call by Value로 구현해보겠습니다.01.
/*
02.
Call by Value Example 1
03.
Programmed by EffortKim
04.
*/
05.
#include <iostream>
06.
07.
void
Swap(
int
data1,
int
data2)
08.
{
09.
int
temp;
10.
11.
temp=data1;
12.
data1=data2;
13.
data2=temp;
14.
}
15.
16.
int
main()
17.
{
18.
int
a=10, b=20;
19.
20.
std::cout<<
"Before\na : "
<<a<<
", b : "
<<b<<std::endl;
21.
std::cout<<
"Swap Function is called.\n"
;
22.
Swap(a, b);
23.
std::cout<<
"After\na : "
<<a<<
", b : "
<<b<<std::endl;
24.
return
0;
25.
}
그럴듯하게 모양이 나오는 것 같습니다. 이 예제는 main함수에 선언된 a와 b의 값을 바꾸기 위하여 Swap함수를 만들고, 이를 전달해 값을 바꾸도록 접근하고 있습니다. 다음은 결과입니다.
아니!!! a와 b의 값이 바뀌어야 하는데, 바뀌고 있지 않습니다.
2.2 Call by Value의 방식
2.1에서 Swap함수를 제대로 구현하지 못했습니다. 위의 결과가 그것을 입증하고 있는데요. 그것은 "Call by Value가 값을 전달하기 때문."이라고 설명할 수 있습니다. 위 예제에서는 변수 a와 b를 바꿀 수 있는 어떤 정보를 주지 않고, a값과 b값 자체를 전달하고 있습니다. 그러므로 a와 b의 값은 바뀌지 않습니다. 즉, Swap함수에서는 변수 a와 b의 값이 아닌, data1과 data2의 값이 교환됩니다. 다음 그림을 보면 이해가 빠를 것입니다.
위 예제에서는 변수 a와 b에 대한 정보(주소값)는 주지 않고 a와 b의 값인 10과 20만 주었습니다. data1과 data2는 "아싸가오리!"라고 외치면서 매개변수로 받았을 것입니다. 그리고 그들(data1과 data2)의 값이 바뀌고 끝이 납니다. 이 때, 변수 a와 b에 대한 정보를 주지 않았으니 변수 a와 b의 값을 교환 하고 싶어도 할 수 없게됩니다. 즉, 교환이 일어나는 메모리 주소는 0x0001과 0x0002가 아닌, 0x0003과 0x0004가 됩니다.
Call by Value는 어떤 함수의 전달인수에 특정 값 자체를 전달하는 방식으로, 특정 값 자체를 전달하다 보니 데이터 자체가 큰 경우 전달하는데 오랜시간이 걸립니다. 대신, 함수의 전달인수로 사용된 데이터 자체는 보호됩니다.
가령, 어떤 무시무시한 악당(갑자기 세균맨이 떠오르네요.)이 위의 스왑함수를 개조하여 스왐함수 내의 data1과 data2에 1을 더하는, 누가 악당 아니랄까봐 정말 악당같은 짓을 행하였습니다. 그래도 data1과 data2의 값만 변할 뿐, main함수 내에 있는 변수 a와 b는 변하지 않습니다.
3. Call by Reference
Call by Value를 통해 Call by Reference에 대해 예측하신 분들이 계셨을 것입니다. Call by Reference는 우리말로 "참조에 의한 호출"로서, 어떤 특정한 값이 아닌 그 값에 대한 정보(이를테면 데이터가 저장된 메모리 번지가 값에 대한 정보라고 할 수 있습니다.)를 전달하는 방식입니다.
2.2의 그림을 이용하자면, Call by Value가 변수 a와 b의 값인 10과 20을 전달했다면, Call by Reference는 변수 a와 b의 메모리 번지인 0x0001과 0x0002를 전달하는 방식이라고 보시면 됩니다.
3.1 스왑함수(Swap Function)의 구현 2
위의 2.1에서 교환되지 않았던 스왑함수를 고쳐보겠습니다.01.
/*
02.
Call by Reference Example 1
03.
Programmed by EffortKim
04.
*/
05.
#include <iostream>
06.
07.
void
Swap(
int
* data1,
int
* data2)
08.
{
09.
int
temp;
10.
11.
temp=(*data1);
12.
(*data1)=(*data2);
13.
(*data2)=temp;
14.
}
15.
16.
int
main()
17.
{
18.
int
a=10, b=20;
19.
20.
std::cout<<
"Before\na : "
<<a<<
", b : "
<<b<<std::endl;
21.
std::cout<<
"Swap Function is called.\n"
;
22.
Swap(&a, &b);
23.
std::cout<<
"After\na : "
<<a<<
", b : "
<<b<<std::endl;
24.
return
0;
25.
}
2.1에서 data1과 data2가 int형이었던 반면, 이번 예제는 int*형이 되었습니다. 먼저 실행 결과 보겠습니다.
우리가 원하는 모양이 되었습니다. Swap함수를 호출하였더니 a와 b의 값이 바뀌었습니다. 아시는 분은 다 아시겠지만, Swap함수에 변수 a와 b 자체를 전달한 것이 아닌, a와 b의 주소값을 전달하여 a와 b에 접근할 수 있도록 하였습니다. 즉, Swap함수 내에서는 data1과 data2의 값을 바꾸는 것이 아닌, data1과 data2가 가리키는 값(a와 b)을 바꾸기 때문에 변수 a와 b의 교환이 일어납니다. 이를 그림으로 표현하면 다음과 같습니다.
우리가 실질적으로 바꾸어야 할 메모리 주소 공간은 0x0001과 0x0002이므로 이 두 주소값을 전달하여 이를 참조(reference)하여 변수 a와 b의 값을 교환할 수 있도록 유도하는 것이 목적입니다.
3.2 C++의 문법 - 레퍼런스(Reference)
사실, 3.2에는 Call by Reference의 방식에 대해 설명하려고 했습니다만, 3.1에서 충분히 설명한 것 같아 C++의 문법 중 하나인 레퍼런스에 대해 설명하겠습니다.
변수이름은 할당된 메모리 공간의 이름과 같습니다. 그래서, 원래는 메모리 주소를 직접 입력하여 메모리에 접근하여야 하지만, 이는 매우 번거롭고 비효율적인 방법이므로, C와 C++는 메모리 공간에 이름을 붙여 이 이름을 부르면 해당 메모리에 접근하도록 하였습니다.
레퍼런스란, 이름을 부여하는 것입니다. 변수를 선언할 때는 메모리 공간을 할당하면서 그 메모리 공간의 이름을 부여하지만, 레퍼런스는 이미 할당된 메모리 공간에 이름만 추가합니다.
쉽게 말해, 어떤 광고에서 어떤 배우가 한 말처럼 "이미 차려진 밥상에 밥숟가락만 얹었을 뿐"에서 밥숟가락이 레퍼런스가 되는 것입니다.
레퍼런스는 이미 할당된 메모리 공간에 대해서만 이름을 추가적으로 부여할 수 있도록 하는 것이기 때문에, 레퍼런스르 선언할 때에는 반드시 변수이름을 대입하도록 합니다. 레퍼런스를 선언할 때에는 &연산자를 사용합니다.1.
// declaration and initialization of variable
2.
int
a=10;
3.
// declaration and initialization of reference
4.
int
& refa=a;
5.
// .......
위 소스코드에서 refa가 레퍼런스입니다. 이 둘을 선언한 다음부터는 a와 refa는 동일한 변수입니다. 이들의 주소값을 조사해도 같은 주소가 나오고, a값이 변하면 refa값도 변합니다.(그 반대도 성립합니다.)
이것을 이용하면, 위의 스왑함수 예제 2(포인터 버전)를 레퍼런스 버전으로 수정할 수 있겠습니다.01.
/*
02.
Call by Reference Example 2
03.
Programmed by EffortKim
04.
*/
05.
#include <iostream>
06.
07.
void
Swap(
int
& data1,
int
& data2)
08.
{
09.
int
temp;
10.
11.
temp=data1;
12.
data1=data2;
13.
data2=temp;
14.
}
15.
16.
int
main()
17.
{
18.
int
a=10, b=20;
19.
20.
std::cout<<
"Before\na : "
<<a<<
", b : "
<<b<<std::endl;
21.
std::cout<<
"Swap Function is called.\n"
;
22.
Swap(a, b);
23.
std::cout<<
"After\na : "
<<a<<
", b : "
<<b<<std::endl;
24.
return
0;
25.
}
레퍼런스는 변수와 동일하므로 포인터와 같이 주소 참조 연산자(*)를 사용하지 않아도 됩니다. 또한, 초기화 할 때 변수 자체를 대입하므로 전달인수로 주소 역참조 연산자(&)를 사용하지 않아도 됩니다. (이 상황에선 사용하면 안됩니다.) 그러므로 소스코드가 포인터 버전에 비하여 깨끗합니다.
3.3 포인터냐!! 레퍼런스냐!! 그것이 문제로다!!!
Call by Reference를 구현할 때 포인터를 사용할 것인지, 레퍼런스를 사용할 것인지 고민하는 경우가 생깁니다. 두가지 방법에 대한 장단점을 소개할 것이니, 이를 보고 스스로 판단 해 보시길 바랍니다.
먼저, 포인터를 이용했을 경우 전달하는 값이 주소값이므로 &연산자를 사용하거나, 포인터를 사용해 인수로 넘겨주어야 합니다. 이를 통해 값의 변경이 있을 것임을 명시적으로 알려주는 셈이 됩니다. 특히, &연산자를 사용하여 변수의 주소를 넘길 경우엔 대놓고 "값을 변경할 것입니다."라고 광고하는 효과를 얻게됩니다. 이렇게 명시적으로 자료의 변경이 있을 것이라고 알려주면 개발자는 이를 보고 에러를 쉽게 찾아내고 디버그 하기가 수월 할 것입니다. 단, 코드가 지저분해지고(주소 참조 연산자, 주소 역참조 연산자를 모두 사용해야 하기 때문입니다.) 할당되지 않은 메모리 공간을 참조할 수 있는 잠재적인 오류가 숨어있습니다.
그리고, 레퍼런스를 이용했을 경우 변수처럼 사용하다 보니, 메모리를 직접적으로 참조하지 않아 할당되지 않은 메모리 공간을 참조할 가능성은 포인터에 비해 줄어듭니다. 코드도 포인터를 사용했을 경우보다는 더 간결해지기도 합니다. 그러나, 어떤 함수의 전달인수로 레퍼런스를 요하는 경우, 그냥 변수 이름만 적어주면 되기 때문에 이 함수가 지금 전달하려는 값을 변경할 것인지, 변경하지 않을 것인지 함수를 해석하기 전에는 알 수 없습니다. 그러므로 변경되면 안되는 변수를 잘못넘겨주어 변경되게 하는 경우에는 속수무책으로 당하기 쉽상입니다. 이런 경우는 대부분 문법적인 오류는 없기 때문에 컴파일러나 링커에 의해서 에러를 찾지는 못하는 경우가 많고, 디버깅을 통해서 알아내야 하는 부분입니다. 또한, 위의 예제에서 Swap(a, b);만 가지고는 이 Swap함수의 전달인수가 레퍼런스인지, 그냥 값인지 알 수 없습니다. 물론,Swap이라는 이름은 값을 변경할 것이라고 알려주긴 하지만, 만일 이름이 Swap이 아닌 Func나 F일 경우에는 선언부나 정의부를 보지 않는이상 값이 변경될 것인지, 변경되지 않을 것인지 알 수 없습니다.
이를 통해 어떤 방법을 써야 할 지 생각해 보셨나요? 위 문제는 Call by Reference에서가 아닌, 어느 경우에나 발생할 수 있는 경우입니다. 그래서 많은 자료에서 위 문제를 많이 언급하고 있습니다. 경우에 따라 좋은 방법을 쓰는 것이 좋겠지요? ^_^ 좋은게 좋은겁니다.
4. 끝마치면서
간략하게 Call by Value와 Call by Reference에 대해 알아보는 시간을 가졌습니다. 이는 매개변수에 값을 전달할 것인가, 값에 대한 참조를 전달할 것인가에 대한 "전달 방법"입니다. 문법적 내용이 아니라는 뜻입니다. 위에서 두 방법을 알아보았을 때 Call by Value와 Call by Reference를 위한 특벌한 문법내용을 언급한 적은 없습니다. 기본적인 문법내용을 토대로 만들어 낸 것입니다. 그러므로 다른 언어(JAVA, C#등)에서도 적용할 수 있는 방법입니다.
단, Viaual Basic에서는 포인터나 그에 상응하는 문법 내용이 없기 때문에 Call by Value와 Call by Reference를 위한 문법이 따로 존재하는 것으로 알고 있습니다. 이에 대한 자세한 정보는 MSDN 페이지인
을 참고하세요.
'Development > C/C++' 카테고리의 다른 글
[LPSTR, LPCSTR, LPTSTR, LPCTSTR , LPWSTR, LPCWSTR 의 의미] (0) | 2012.08.19 |
---|---|
[C언어 문법: Bit field (콜론 연산자)] (0) | 2012.06.16 |
[Event] (0) | 2011.12.01 |
[Semaphore] (0) | 2011.12.01 |
[Mutex] (0) | 2011.12.01 |