함수 리턴값 반환 방식, 참조 리턴 함수

김펭귄·2025년 5월 6일

C++

목록 보기
20/20

1. 메모리에서의 함수 구조

  • 함수가 실행되면 메모리의 stack에 공간을 차지하며 실행
  • 함수가 종료되면 생성되었던 스택이 해제됨
  • 이때 리턴값이 함수를 호출한 쪽으로 전달됨

1-1. 리턴값이 값(value)

int func() {return 5;}

int main() {
	int a = func();
}
  • 함수의 리턴값이 레지스터(EAX, RAX 등)에 저장
  • 리턴값이 담긴 레지스터의 값이 호출한 함수로 전달
  • 호출한 쪽에서 값을 자신의 변수에 복사

1-2. 리턴값이 포인터(pointer), 참조(&)

  • 값과는 다르게 실제 데이터가 있는 메모리 주소(혹은 참조)가 반환됨
  • 함수 종료 시 메모리 해제되므로 지역 변수의 주소/참조를 반환하면 안됨

1-3. 리턴값이 객체같이 클 경우

  • 컴파일러가 호출자 쪽에서 임시 메모리를 준비
  • 함수가 그 메모리를 리턴값으로 복사

2. 참조 리턴 함수

  • 함수에서 참조 타입(T &)을 반환하면, 함수 호출 결과로 "값"이 아니라 원본 변수의 별명(참조)을 넘겨줌
  • 반환 결과가 값이 아니라 변수 그 자체 (포인터와 비슷한 느낌)
  • 즉, 함수가 반환한 참조를 통해 원본 변수에 직접 접근하거나 수정할 수 있음
  • 함수의 지역변수를 반환할 경우 함수 종료시 사라지므로 안 됨
class A {
    int x;
public:
    A(int c) : x(c) {}
    int& access_x() { return x; }   // x의 참조(x 자체)를 리턴
    int get_x() { return x; }       // x의 값을 리턴
    void show_x() { std::cout << x << std::endl; }
};

int main() {
    A a(5);
    a.show_x();            // 5
    int& c = a.access_x(); // c는 x의 또다른 별명
    c = 4;                 // x의 값이 4로 변경됨
    a.show_x();            // 4
    int d = a.access_x();  // x의 값을 복사
    d = 3;                 // x에는 영향 없음
    a.show_x();            // 4
}
  • int& c = a.access_x();
    → c는 int&이므로 x와 완전히 동일한 메모리 공간을 가리킴
    → c의 값을 바꾸면 a의 x도 바뀜

  • int d = a.access_x();
    → d는 int이므로 그냥 x의 값을 복사함
    → d를 바꿔도 x에 영향 없음

2-1. 참조 리턴 함수의 장점

  • 대용량 데이터(구조체 등)를 복사하지 않고 효율적으로 반환
  • 멤버 변수의 직접 수정이 필요할 때 유용
  • 포인터와 달리 문법이 간결하고 안전

2-2. 주의사항

  • 지역 변수의 참조를 리턴하면 안 됨
  • 함수 종료 후에도 존재하는 메모리(멤버 변수, 동적할당 등)만 리턴
  • 값을 리턴하는 함수의 반환값을 참조 받으면 에러
  int &e = a.get_x();  // int& e = 4 에러

2-3. 추가 활용법

  • 참조 리턴의 경우 그냥 변수를 리턴한 것과 동일
  a.access_x() = 3;  // a.x = 3과 동일
  a.access_x()++;    // a.x++과 동일
  • 멤버 함수가 객체 자신의 참조(*this)를 반환하면 연속 호출 가능
  class A {
	int x;
    
  public:
	A(int c) : x(c) {}
	A& decrease() {  // 참조 리턴 함수
		x--;
		return *this;  //  자신의 참조를 반환
	}
	void show_x() { std::cout << x << std::endl; }
  };

  int main() {
	A a(10);  // x = 10
	a.decrease().decrease();  // 연속 호출 
	a.show_x();  // 8출력
  }
  1. 처음 a.decrease()의 결과 a.x의 값이 감소하고 객체 a가 반환됨
  2. a가 반환되었으므로 연속으로 decrease() 실행가능

  • 만약, 참조 리턴이 아닐 경우
  class A {
	int x;
    
  public:
	A(int c) : x(c) {}
	A decrease() {  // 참조 리턴 아닐 경우
		x--;
		return *this;  //  자신의 참조를 반환
	}
	void show_x() { std::cout << x << std::endl; }
    ~A() {std::cout << "소멸자" << std::endl;}
  };

  int main() {
	A a(10);  // x = 10
	a.decrease().decrease();  // 연속 호출이지만
	a.show_x();  // 9출력
  }
  1. 객체 a의 메모리 주소가 decrease()로 들어가 함수 실행
  2. 처음 a.decrease()의 결과 a.x의 값이 감소하는 것까지는 동일
  3. 그러나 반환값이 객체이므로 main에서 새로운 메모리(임시 객체)를 생성, 반환 객체를 복사 (x=9). 이 메모리는 기존 a와는 개별적인 임시 객체임
  4. 그리고 이 임시 객체로 다시 decrease() 실행
  5. 함수 실행 시 임시 객체의 x가 감소, a는 그냥 유지
  6. 다시 리턴값 받기 위해 main에서 임시 객체를 생성하고 반환 객체를 복사
  7. a.decrease().decrease()가 끝나면 다시 임시 객체 2개는 소멸자 호출되며 소멸됨
profile
반갑습니다

0개의 댓글