C++/게임 개발자를 위한 C++ 문법

함수 인자 전달 방식

iiblueblue 2024. 12. 23. 20:40
⊙ 값에 의한 전달과 참조에 의한 전달을 이해하고 차이를 안다.
⊙ 참조 변수에 대해 안다.
⊙ 함수에 전달되는 배열의 특징을 이해한다.
⊙ 상수 참조 매개변수를 알고 필요성을 이해한다.

 

함수에 인자를 전달하는 방식에는 세 가지가 있다.

  • 값에 의한 전달(call by value)
  • 참조에 의한 전달(call by reference)
  • 주소에 의한 전달(call by address)

 

값에 의한 전달(call by value)

함수에 인자를 전달할 때, 인자의 복사본이 함수로 전달된다. 따라서 원래의 변수는 함수의 영향을 받지 않는다.

  • 함수 내부에서 값을 변경해도 원래 변수에는 영향을 미치지 않는다.
  • 복사 과정이 발생하므로 데이터 크기가 크면 성능에 영향을 줄 수 있다.

이러한 특징 때문에 원래 변수의 안정성을 보장할 수 있다는 장점이 있는 반면 데이터 복사로 인해 메모리 사용량이 증가할 수 있다는 단점 또한 가지고 있다.

값에 의한 전달 과정

값에 의한 전달이 일어나는 과정을 아래 예시 코드로 알아보자.

#include <iostream>

using namespace std;

void increment(int n)
{
	n++;
	cout << n << endl; // 2 출력
}

int main()
{
	int x = 1;
	cout << x << endl; // 1 출력

	increment(x);
	cout << x << endl; // 1 출력
	return 0;
}

변수 x를 값에 의한 전달로 함수에 전달할 때 매개변수 n의 값이 변하면 x의 값도 변하는지 확인해볼 수 있는 코드이다. 이 코드는 실행하면 x는 그대로 값이 변경되지 않고 1이 출력되고 n만 값이 변경되어 2를 출력한다.

 

x와 n은 서로 다른 독립적인 공간을 사용한다. 메모리 주소가 다른 서로 완전히 다른 집에서 살고 있는 것이다. 다만 x가 전달될 때 n이 x 집안의 인테리어 구조를 전부 똑같이 배껴 온 것이다. 똑같이 배껴오긴 했지만 둘은 다른 집에서 살고 있기 때문에 n이 자기 집 가구를 조금 바꾼다고 해서 x의 집과는 아무 상관없을 것이다.

 


 

참조에 의한 전달(call by reference)

일단 참조에 의한 전달에 대해 알아보기 전에 참조 변수란 무엇인지부터 알아보자.

참조 변수

원 변수에 대한 별칭으로 참조 변수를 수정하면 원 변수에도 수정된 내용이 반영된다. 좀 더 깊숙히 들여다보자. 참조 변수를 선언하면 해당 참조는 원래 변수와 동일한 메모리 주소를 가리키게 된다. 즉, 참조 변수는 원래의 변수와 메모리 공간을 공유하는 것이다.

쉽게 설명하면 한 장소에 그 장소를 부르는 별명이 추가되는 것이다. 우리집 주소를  도로명 주소로 부르지만 지번 주소로도 부를 수 있는 것과 같다. 도로명 주소든 지번 주소든 결국 우리집을 가리키고 있으니 내 집에서 가구를 바꾸면 도로명 주소로 오던 지번으로 오던 가구는 바뀌어 있는 것이다.

int& r = count;
int & r = count;
int &r = count;

참조 변수는 위와 같이 &를 데이터 타입과 변수명 사이에 적어 선언할 수 있다. 보이는 것처럼 &가 오른쪽, 가운데, 왼쪽 어디에 붙어있든 동작한다. 단 int& r 뒤에는 무조건 원 변수가 무엇인지 적어야만 한다. 즉, 반드시 선언과 동시에 초기화해야 한다.

#include <iostream>

using namespace std;

int main()
{
	int count = 1;
	int& c = count;

	cout << count << "" << c << endl;

	count = 0;
	cout << count << "" << c << endl;

	return 0;
}

이 코드를 예시로 살펴보자. count라는 변수를 선언하고 1로 초기화 하였다. 그리고 c라는 이름으로 참조변수를 선언하였다. 여기서 count와 c는 같은 변수이다. c는 count의 별칭일 뿐이기 때문이다. 

그렇기 때문에 count 값을 0으로 바꿔주면 c도 따로 아무 조치도 하지 않았는데 함께 0으로 바뀌어 있는 것이다.

 

이제 참조에 의한 전달에 대해 알아보자.

함수에 변수의 참조를 전달한다. 이 방식에서는 변수 자체가 전달되므로, 함수 내부에서 값을 변경하면 원래 변수에도 영향을 준다는 것이 큰 특징이다.

  • 함수 내부에서 변수의 값을 변경할 수 있으므로, 의도하지 않은 부작용이 발생할 수 있다.
  • 데이터 복사가 일어나지 않으므로 성능이 향상된다.
  • 문자열 같은 객체 유형에 대해서는 참조에 의한 전달이 값에 의한 전달보다 효과적이다.

 

참조에 의한 전달 과정

참조에 의한 전달이 일어나는 과정을 예제 코드로 알아보자.

#include <iostream>

using namespace std;

void increment(int& n)
{
	n++;
	cout << "n inside the function is " << n << endl; // 2 출력
}

int main()
{
	int x = 1;
	cout << x << endl; // 1 출력

	increment(x);
	cout << x << endl; // 2 출력

	return 0;
}

 

함수의 매개변수로 참조에 의한 전달된 x의 값이 어떻게 변화하는지 출력으로 확인할 수 있는 코드이다. 함수 increment에서 x의 참조 변수인 n의 값을 변경했을 과연 x도 함께 변경할까? x가 함수로 매개변수로 전달되면 int& n = x이 된다. 따라서 n은 변수 x의 별명이기 때문에 n의 값이 변하면 x의 값이 함께 변하게 된다. 왜 이런 일이 일어나는지  좀더 자세히 알아보자.

 

x와 n은 메모리에서 같은 공간을 사용한다. 말 그대로 별칭인 것이다. x를 보고 메모리를 찾아가던 n을 보고 메모리를 찾아가던 둘다 같은 곳을 쉐어하고 있기 때문에 n에서 값을 바꾸면 같은 주소를 보고 있던 x도 바뀌는 것이다. n과 x는 같은 집에 살고 있는데 n이 그 집 인테리어를 바꾸면 x도 인테리어가 바뀐 그 집에서 같이 살아야 한다고 생각하면 된다. x도 동의한 시공이면 상관없지만 그게 아니라면 프로그램에 문제가 생길 것이다.

 


 

상수 참조 매개변수

참조에 의한 전달을 할 때 가장 큰 문제점은 내가 의도하지 않았는데 원 변수의 값이 바뀌어 버리는 것이다. 그런 경우 내가 원하는 기능을 하지 않을 것이다.

참조에 의한 전달 매개변수를 사용하지만, 매개변수 값이 함수 안에서 변경되지 않아야할 때 매개변수의 값이 변경되지 않도록 매개변수를 상수(const)로 지정하면 된다.

#include <iostream>

using namespace std;

int max(const int& num1, const int& num2)
{
	int result;
	if (num1 > num2)
		result = num1;
	else
		result = num2;
        
	//num1 = 2; // 오류 발생

	return result;
}

int main()
{
	int a = 4;
	int b = 8;

	max(a, b);

	return 0;
}

위 예제처럼 num1과 num2의 매개변수에 const를 붙여 상수로 만들면 변수 안에서 값의 변경이 일어나면 오류가 발생하게 된다. 이런 방법으로 의도치 않은 값의 변경을 막을 수 있다.

 


 

주소에 의한 전달(call by address)

함수에 변수의 주소(pointer)를 전달한다. 이를 통해 함수는 워낼 변수의 메모리 위치를 참조하므로, 변수의 값을 직접 변경할 수 있다.

  • 참조에 의한 전달과 비슷하지만, 함수에서 포인터를 사용해야 한다.
  • 널 포인터(NULL pointer)에 대한 처리가 필요하다.

참조에 의한 전달과 마찬가지로 데이터 복사 비용이 없다는 것은 장점이다. 또한 포인터를 사용하여 동적 메모리 할당이나 배열과 같은 데이터 구조를 다룰 수 있다는 것도 장점이다. 반면 포인터 사용으로 인해 코드가 복잡해 보인다는 것과 만약 잘못된 포인터 접근이 일어나면 런타임 에러가 발생할 가능성이 있다는 것은 단점이다.

 

주소에 의한 전달 과정

주소에 의한 전달 과정을 아래 예시 코드로 알아보자.

#include <iostream>

using namespace std;

void increment(int* n)
{
	*n++;
	cout << n << endl; // 2 출력
}

int main()
{
	int x = 1;
	cout << x << endl; // 1 출력

	increment(&x);
	cout << x << endl; // 2 출력
	return 0;
}

변수 x가 선언되고 그 변수가 increment 함수의 매개변수로 들어가 값의 변형이 있을 때 원본의 x값에도 변형이 있는지 알아볼 수 있는 코드이다. x는 1로 초기화가 되어 선언되었으며 increment 함수는 매개변수로 포인터 변수를 받아 값을 변형하고 있다. 이 때 x의 주소(&x)를 increment의 매개변수로 하여 함수를 호출한다. increment는 매개변수의 값을 1 증가시키는 함수이다. 결과값을 출력했을 때 매개변수 n의 값은 당연히 2로 늘어났고 x의 값 또한 2로 늘어난 것을 확인할 수 있다.

 

참조에 의한 전달과 비슷한 결과가 나온다. 왜냐하면 x가 들어가 있는 그 주소로 직접 가서 값을 변경한 것이기 때문이다. n에게 x의 주소를 알려주고 직접 찾아가 값을 바꾸라고 했으니 당연히 x의 값도 변경된다. 함수에서 포인터를 사용한다는 것이 다를 뿐 결과는 참조에 의한 전달과 같음을 볼 수 있다.

 


 

함수로 배열 전달

위예 예시도 그렇고 변수와 객체 모두 포인터가 달려 있는지, 참조 변수를 쓰고 있는지, 아무 표시도 없는지 확인하면 어떤 복사 방식을 사용하여 함수에 인자를 전달하는지 알 수 있다. 하지만 그 중 그냥 보고는 이게 무슨 복사 방식을 사용하고 있는지 헷갈릴 만한 인자가 있는데 그게 바로 배열이다. 아래 코드를 살펴보자.

void changeNumber(int number, int numbers[])
{
	number=1001;
    numbers[0]=555;
}

int main()
{
	int x=1; // 싱글 변수
    int y[10]; // 배열
    y[0]=1;
    
    changeNumber(x, y); // 변수와 배열 값 변경
    
    // 결과 확인
    cout<<x<<endl;
    cout<<y[0]<<endl;
    
    return 0;
}

매개변수 number은 포인터 변수도 참조 변수도 아니기 때문에 그냥 값에 의한 전달을 한 것을 알 수 있다. 하지만 numbers[]는 어떨까? 아무것도 붙어있지 않은 것을 보면 값에 의한 전달을 한 것처럼 보이기도 한다. 하지만 놀랍게도 사실 이건 주소에 의한 전달을 하고 있다.

 

배열의 이름은 두 가지 역할을 한다. 말 그대로 배열의 이름이기도 하면서 사실 배열의 시작 주소이다. 즉, y는 &y[0]과 같다. 결국 인자로는 배열의 시작 주소를 전달한 것이 된다. 함수에 배열을 전달하면 배열의 주소가 전달되어, 함수 내부에서 매개변수의 값을 수정하면 배열의 원본을 수정하게 되는 것이다. 따라서 이 코드의 결과는 x는 변하지 않고 1을 출력하고 y[0]은 값이 변한 555를 출력하게 될 것 이다.

 


 

배운 내용 정리

  • 값에 의한 전달로 인자를 전달하면 매개변수 값이 변경되더라도 원본 값에는 영향을 주지 않는다.
  • 참조에 의한 전달로 인자를 전달하면 매개변수 값이 변경되면 원본 값도 같이 변경된다.
  • 참조에 의한 전달을 했지만 인자를 변경하고 싶지 않을 때는 매개변수에 const를 붙여 상수 참조 매개변수로 만들어 사용한다.
  • 주소에 의한 전달은 참조에 의한 전달과 비슷하게 매개변수 값이 변경되면 원본 값이 같이 변경된다.
  • 함수로 배열 인자를 전달하면 다른 표시가 없어도 배열의 이름이 배열의 시작주소를 의미하기 때문에 주소에 의한 참조를 하게 된다.

'C++ > 게임 개발자를 위한 C++ 문법' 카테고리의 다른 글

다중 포함 방지  (0) 2024.12.26
포인터의 개념과 사용  (0) 2024.12.26
동적 메모리 할당과 해제  (0) 2024.12.24
연산 주의사항  (0) 2024.12.23
Array와 Vector의 차이점과 사용법  (1) 2024.12.23