C 또는 C++에서는 함수가 여러 개의 값을 직접 반환할 수 없습니다. return 문은 기본적으로 하나의 값만 돌려줄 수 있기 때문인데요, 다행히 몇 가지 트릭을 활용하면 하나의 함수 호출만으로 두 개 이상의 결과를 얻을 수 있습니다.
대표적인 방법은 주소 전달(Call By Address)과 참조 전달(Call By Reference)입니다. 두 방식 모두 호출자(caller) 쪽에 결과를 저장할 변수를 미리 준비해 두고, 함수가 해당 변수의 메모리 공간에 직접 값을 기록하는 원리로 동작합니다. 주소 전달은 포인터를, 참조 전달은 C++의 참조자(&)를 사용한다는 점이 차이입니다.
이 글에서는 두 수를 나누었을 때의 몫(quotient)과 나머지(remainder)를 단 하나의 함수에서 동시에 구해 반환하는 예제를 통해 두 방식을 살펴보겠습니다.
방법 1: 주소 전달(Call By Address)
포인터 타입의 매개변수를 선언하고, 호출 시 인수의 주소값을 전달합니다. 함수 내부에서는 역참조 연산자(*)를 사용해 해당 메모리 위치에 직접 결과를 저장하며, 함수가 종료된 후에도 호출자의 변수에 값이 그대로 남아 있습니다.
예제 코드
#include<iostream>
using namespace std;
void div(int a, int b, int *quotient, int *remainder) {
*quotient = a / b;
*remainder = a % b;
}
main() {
int a = 76, b = 10;
int q, r;
div(a, b, &q, &r);
cout << "Quotient is: " << q << "\nRemainder is: " << r << "\n";
}실행 결과
Quotient is: 7 Remainder is: 6
div() 함수를 호출할 때 q와 r의 주소(&q, &r)를 넘겨주면, 함수 내부의 포인터 quotient와 remainder가 이 변수들을 가리키게 됩니다. 따라서 *quotient와 *remainder에 값을 대입하면 그 결과가 호출자의 q, r에 그대로 반영됩니다.
방법 2: 참조 전달(Call By Reference)
C++에서만 사용할 수 있는 방식으로, 포인터 대신 참조자(reference) 매개변수를 사용합니다. 함수 선언부의 매개변수에 & 기호만 붙여주면 되고, 호출 시에는 변수를 그대로 전달하면 됩니다. 함수 내부에서도 역참조 없이 일반 변수처럼 사용할 수 있어 문법이 훨씬 간결하고 안전합니다.
예제 코드
#include<iostream>
using namespace std;
void div(int a, int b, int "ient, int &remainder) {
quotient = a / b;
remainder = a % b;
}
main() {
int a = 76, b = 10;
int q, r;
div(a, b, q, r);
cout << "Quotient is: " << q << "\nRemainder is: " << r << "\n";
}실행 결과
Quotient is: 7 Remainder is: 6
참조자는 원본 변수의 별칭(alias)처럼 동작하기 때문에, 함수 내부에서 quotient와 remainder에 값을 대입하는 순간 호출자의 q와 r에 바로 저장됩니다. 포인터를 다루는 번거로움이 없어 실무에서도 널리 선호되는 방식입니다.
마치며: 현대 C++의 대안들
두 방식 모두 동일한 결과를 만들어내지만, 최신 C++에서는 값을 실제로 반환하는 더 우아한 방법들도 있습니다.
- std::pair / std::tuple: 여러 값을 하나의 객체로 묶어 반환할 수 있습니다.
- 구조체(struct): 의미 있는 여러 값을 하나의 사용자 정의 타입으로 정의해 반환합니다.
- 구조적 바인딩(C++17):
auto [q, r] = div(a, b);형태로 반환값을 손쉽게 분해해 받을 수 있습니다.
다만 포인터나 참조를 이용한 출력 매개변수(output parameter)는 레거시 코드와 C 스타일 라이브러리에서 여전히 널리 사용되고 있으므로, 두 방식의 동작 원리와 차이를 정확히 이해해 두는 것이 좋습니다.