[C/C++] 함수의 리턴값으로 주소(포인터) 참조자를 받을 시 유의점

    반응형

    *개인적인 공부 내용을 기록하는 용도로 작성한 글 이기에 잘못된 내용을 포함하고 있을 수 있습니다.

    #1 Return By Value

    #2 Return By Address / Reference


    #1 Return By Value

    getValue 함수는 값(Value)을 반환하는 간단한 Return-By-Value 함수입니다. Return-By-Value 함수는 복사 과정이 많이 일어나서 비효율적이라는 단점이 있습니다. 물론 아래 코드처럼 간단한 케이스는 상관 없지만 배열이나 구조체 등 데이터가 많은 자료형을 반환할 때는 문제가 발생할 수 있습니다.

     

    #include <iostream>
    using namespace std;
    
    // return by value
    int getValue(int x)
    {
    	int value = x * 2;
    	return value;
    }
    
    int main(){
    	int value = getValue(3);
    	return 0;
    }

     

    #2 Return By Address / Reference

    다음으로 함수의 반환값으로 포인터 즉, 주소를 반환하는 케이스입니다.

    #include <iostream>
    using namespace std;
    
    // return by address
    int* getAddress(int x)
    {
    	int value = x * 2;
    	return &value;
    }

     

    getAddress 함수의 리턴값을 받는 방법은 2가지를 떠올릴 수 있습니다. 첫 번째는 *(de-referencing) 연산자를 이용해 리턴값을 받는 방법입니다.

    int main(){
    	int value = *getAddress(5);
    	return 0;
    }

     

    하지만 이 방식은 위험한 생각입니다. 왜냐하면 getAddress 함수의 지역변수 value는 getAddress 함수가 종료되면 메모리가 사라지기 때문입니다. 이처럼 사라질 변수를 de-referencing 하는 것은 큰 위험이 있습니다.

     

    실제로 value에 들어있는 값을 출력해 보면 가비지 값(예상치 못한 쓰레기값)이 출력됩니다.

    #include <iostream>
    using namespace std;
    
    // return by address
    int* getAddress(int x)
    {
    	int value = x * 2;
    	return &value;
    }
    
    int main(){
    	int value = *getAddress(5); // 사라질 변수를 de-referecing 하는 것은 위험이 있음.
     	 cout << value << endl;
    	return 0;
    }
    -831371064

     

    혹은 포인터 변수를 이용해 변수의 주소값을 받는 방법을 떠올릴 수 도 있습니다. 하지만 이 방식은 더 위험합니다.

    사라질 변수(getAddress의 value)의 주소값을 담고 있는 상황이 되어 버리기 때문입니다.

    #include <iostream>
    using namespace std;
    
    // return by address
    int* getAddress(int x)
    {
    	int value = x * 2;
    	return &value;
    }
    
    int main(){
    	int *value = getAddress(5);
    	return 0;
    }

     

    참조자(&)의 경우도 마찬가지입니다. 아래처럼 코드를 작성하면 가비지값으로 초기화될 수 있습니다.

    //return by refference
    int& getRef(int x)
    {
    	int value = x * 2;
    	return value;
    }
    
    int main(){
    	int value = getValue3(5);
    	int &value = getValue3(5); 
    	return 0;
    }

     

    이처럼 리턴값이 임시적으로 생겼다가 사라지는 지역변수인 경우 위에 소개한 방식처럼 리턴하는 것은 예상치 못한 결과를 불러 일으킬 수 있습니다.

     

    단, 아래처럼 메모리가 함수의 실행 여부에 영향을 받지 않고, 확실하게 잡혀있는 상태라면 참조자만을 보내는 것은 괜찮습니다.

    int& get(std::array<int, 100>& my_array, int ix)
    {
    	return my_array[ix];
    }
    
    int main(){
    	std::array<int, 100> my_array;
    	get(my_array, 30) = 30;
    	return 0;
    }

     

    * 정리

    1. Return By Value 방식은 간편하지만 복사가 많이 일어나 비효율적이라는 단점이 있다. 작은 크기의 데이터를 리턴하는 것은 문제가 되지 않지만 다수의 데이터를 갖고 있는 배열이나 구조체 같은 자료형을 리턴 시에는 문제가 될 수 있다.

    2. Return By Address/Reference 방식의 함수에서 리턴값이 임시적으로 생겼다가 사라지는 지역변수인 경우 예상치 못한 결과를 불러 일으킬 수 있다. 단, 메모리가 함수의 실행 여부에 영향을 받지 않고 확실하게 잡혀 있는 상태에서 주소값/참조자를 보내는 것은 괜찮다.

    반응형

    댓글

    Designed by JB FACTORY