본문 바로가기

Program Language and Algorithm

Call by Value and Call By Reference

반응형

 메모리 직접 접근이 가능한 소프트웨어 언어의 함수 호출 방법은 두 가지입니다. 하나는 call by value이고, 하나는 call by reference입니다. 자세한 개념은 전공서적이나 위키피디아를 통해서 습득하시는 것으로 하고, 이 글에는 간단한 설명과 예제가 따라옵니다.

 call by value는 입력 아귀먼트를 복사하는 함수입니다. 입력 아귀 먼트와 같은 데이터형의 지역 변수를 만들고, 값을 복사합니다.

 call by reference는 입력 아귀먼트의 주소 값을 참조합니다. 따라서 호출된 함수에서 아귀 먼트의 값을 변경하면, 호출 한 함수에서도 변경됩니다. 포인터를 전송하는 것과 같죠.

 그럼 설명은 요렇게 간단하게 마치고, 예제로 넘어 가겠습니다.

#include <iostream>
#include <vector>

using namespace std;

#define PRINT(AA) cout << __FUNCTION__ << " " << __LINE__ << "\t> " << # AA << " : " << AA << endl

int callByValue(int a, vector<int> b){
    PRINT(&a);
    PRINT(&b);

    int res = a+1;
    
    for(int i = 0 ; 10 > i ; i++){
	b.push_back(i);
    }

    cout << "b : ";
    
    for(auto i : b){
	cout << i << " ";
    }
    cout << endl;

    return res;
}

int callByReference(int &a, vector<int> &b){
    PRINT(&a);
    PRINT(&b);

    int res = a+1;

    for(int i = 0 ; 10 > i ; i++){
	b.push_back(i);
    }

    cout << "b : ";
    
    for(auto i : b){
	cout << i << " ";
    }
    cout << endl;

    return res;
}

int main(int argc, char ** argv){

    int a = 1;
    vector<int> b;

    PRINT(&a);
    PRINT(&b);

    auto printB = [&b](){
	cout << "b : ";
	for(auto  i : b){
	    cout << i << " ";
	}
	cout << endl;
    };

    for(int i = 0 ; 5 > i ; i++){
	b.push_back(i);
    }

    cout <<  callByValue(a, b) << endl;
    printB();
    cout <<  callByReference(a, b) << endl;
    printB();

    return 0;
}

 향후 정식으로 다룬 글을 쓰게 될지 모르겠으나, define 구문에서의 함수 및 입력 아귀 먼트 응용, gcc의 키워드, 람다 등이 이용되었습니다. 이 부분은 간단히 설명하도록 하겠습니다.

 define 구문으로 함수를 만들 때는 함수명(입력 아귀 먼트)으로 구성됩니다. 입력 아귀 먼트의 데이터형은 필요 없습니다. 시작과 끝을 나타내는 괄호는 없고, 해당 열이 구문이 됩니다. \를 이용하면 다수의 열을 이용할 수 있습니다. 아귀먼트의 사용법은 보통 함수와 같으나, 두 가지 추가 사용법이 있습니다. 첫 번째는 #을 붙여서 문자열로 이용할 수 있으며, ##을 이용해서 다른 문자열과 조합해서 다른 변수에 접근할 수 있습니다.

 두 번째로 define 구문에서 이용된 __FUNCTION__과 __LINE__은 컴파일러에서 이용되는 키워드입니다. __FUNCTION__은 키워드가 호출된 함수명으로 변환되고, __LINE__은 호출된 줄로 변경됩니다. (위 예제에서는 define 구문은 컴파일 시 치환됨으로 define을 호출한 위치의 함수와 줄 수가 출력됩니다. )

 람다는 나중에 조금 더 정리되면 다른 글로 정리하려고 합니다. 간단히 설명하면 더 범용성이 높고 빠른 함수를 만드는 방법입니다. 위 예제에서는 vector b의 원소 값들을 출력합니다.

 

 코드의 구조는 간단합니다. 먼저 프로그램이 구동되면 main함수에서 int형 변수 a와 int형 vector b를 만들어 줍니다. 그 후 a와 b의 주소를 출력합니다. b에는 0부터 4까지의 원소를 추가합니다. 이후 callByValue 함수와 callByReference 함수를 호출합니다. 입력 파라미터는 a와 b를 이용합니다. 각 함수의 호출 이후에는 반환 값과 b의 원소들을 출력합니다.

 callByValue와 callByReference함수는 입력 아귀 먼트의 전달 방식을 제외하면 정확히 동일한 동작을 합니다.  먼저 두 입력 아귀먼트의 주소 값을 출력합니다. 그후 새로운 지역변수 res에 a+1을 저장한 후, b에 0부터 9까지의 변수를 추가합니다. 그후 b에 저장한 원소들을 모두 출력 합니다. 마지막으로 출력 아귀 먼트로 res를 반환합니다.

 위 코드의 구동 결과는 아래 스냅숏입니다.

 먼저 세 함수의 변수들에 주소 값을 확인해 보면 main과 callByReference의 주소 값은 동일합니다. 함수 호출 시 변수의 값을 복사하지 않고, 같은 메모리의 값을 이용하는 거죠. callByValue는 입력 파라미터들을 지역 변수에 복사해서 사용하기 때문에 메모리 주소가 달라지게 됩니다.

 위 구동 구조로 인해 callByValue함수의 반환 아귀 먼트는 2로 전달되지만 vector b의 값은 달라지지 않습니다. 입력 아귀 먼트로 b가 전달되긴 하지만 값을 복사해서 지역 변수에서 처리했기 때문입니다. 그래서 함수 내부에서 b는 15개의 원소를 가지지만 main함수에서는 5개만 있습니다.

 callByReference함수는 동일한 메모리의 변수를 이용하기 때문에, 갱신한 vector b의 값이 main함수의 b에도 적용됩니다.

 

 예제에 대한 설명은 끝났습니다. 원래 제가 글을 쓰는 패턴으로는 여기서 마치겠으나, 이 주제에서는 꼭 짚어야 할 것이 남아 있습니다.

 call by value는 입력 아귀 먼트의 값을 지역 변수에 모두 복사 합니다. 위 예제에서도 vector b의 원소값 5개가 모두 복사 되었죠. 따라서 call by reference에 비해 더 많은 메모리를 이용합니다. 하지만 그렇다고 해서 call by reference가 call by value 보다 좋은 구조라고 할 수 없습니다. call by reference는 함수가 출력 아귀먼트가 아닌 입력 아귀먼트의 값을 변경해 버리기 때문입니다. 위 예제에서도 call by reference는 main 함수의 vector b의 값을 변경했습니다. 물론 설계상 유도한 효과라면 좋은 결과 이겠지만, 설계상의 오류가 될 수도 있습니다. 상황에 따라 적합한 구조의 호출 방식을 이용합시다.

 

 그럼 마치겠습니다. 감사합니다.

반응형