본문 바로가기

프로그래밍/C++

[C++] Call-By- ······ 그게 뭔데

들어가며

원래는 본격적으로 참조자의 활용에 대해 학습할 시간이지만,

오늘 배울 이 개념을 확실하게 잡고 가지 않으면 안된다.

'또,또,또,또,또, 지만 아는 거 나왔다고 신났죠?'

누누히 말하지만 이 블로그가 누구한테 보여지는 것을 상정하고

본 필자가 글을 쓰는 것은 아니기 때문에

혼자 공부하는 느낌으로 운영하는 블로그다.

실제로는 꽤 보는 사람이 있는 거 같지만,

어찌 되었던 간에 내 일기장을 누가 보는 느낌이라고 생각하면 편하다.

각설하고, 사실 참조자의 활용에는 함수가 큰 지분을 차지하고 있기 때문인데,

따라서 오늘은 함수의 호출 방식에 관한 것을 학습해볼까 한다.

 

함수 호출의 두 가지 방식

'Call-By-Value & Call-By-Reference'

어디서 분명 많이 들어본 구절이다.

포인터를 공부할 때 들어본 것 같기도 하고,

레퍼런스라고 하니 지금 우리가 배우는 참조자의 영문명이 'Reference'이지 않는가?

그렇다.

이 둘은 우리가 C를 공부하면서 배운 함수의 두가지 호출 방식이다.

각각의 의미와 특징은 다음와 같다.

그림 8 - 두 가지의 함수 호출 방식

우선, 참조자의 활용을 학습하기에 앞서 이에 대해서 짚고 넘어가지 않을 수가 없다.

 

참고로, 아래에는 두 가지 방식에 대해 아주아주 자세히 서술할 예정이다.

이 개념에 대해 자신이 충만하다 못해 내가 C 및 C++의 표준을 좌지우지할 정도의

위대하고 저명하며 권위적 위치에 있는 프로그래머 혹은 연구자가 아니라면

대충이라도 쓱 읽고 넘기길 바란다.

 

Call-By-Value

우선 Call-By-Value 기반의 함수는 다음과 같이 정의된 함수라고 볼 수 있다.

int Adder(int num1, int num2)
{
    return num1 + num2;
}

 

위 함수는 두 개의 int형 정수를 인자로 요구하고 있다.

따라서 Call-By-Value 기반의 함수라고 볼 수 있다.

 

그러니까, Call-By-Value의 형태로 정의된 함수의 내부에서는

함수 외부에서 선언된 변수에 대해서 접근이 불가능하다.

즉, 두 변수에 저장된 값을 서도 바꿔서 저장할 목적으로

다음과 같이 함수를 정의하면 원하는 결과를 얻을 수 없다.

int SwapByValue(int num1, int num2)
{
    int temp = num1;
    num1 = num2;
    num2 = temp;
}   // Call-By-Value 방식

 

실제로 이 함수를 main함수에서 실행하게 된다면

int main()
{
    int val1 = 10;
    int val2 = 20;

    SwapByValue(val1, val2);  // val1의 값과 val2의 값이 서로 바뀌길 원함
    cout << val1 << endl;
    cout << val2 << endl;

    return 0;
}

 

히히 어림도 없지!

기대와는 다르게 출력되는 값은 정직하게도 각각 10과 20이 출력된다.

이것이 의미하는 바는 val1의 값과 val2의 값이 서로 바뀌지 않았음을 의미하지 않겠는가?

그림으로 설명하자면 다음과 같다.

그림 9 - 평화로운 main함수

우선 main함수에 두 개의 변수가 선언되었다.

다음은 SwapByValue 함수의 호출이지 않은가?

그림 10 - SwapByValue 함수의 몸체 실행 직전

함수의 몸체를 실행하기 직전에는 SwapByValue 함수의 상황은 그림 10과 같다.

num1과 num2는 SwapByValue 함수에서 '지역적'으로 선언된 변수들이다.

그리고 전달된 val1, val2를 인자로 넘겨받아 각각의 변수에 저장한 모습이다.

그리고 SwapByValue를 완전히 실행시키면 다음과 같다.

분명하게 num1과 num2의 값이 서로 교체되어 있는 모습을 확인할 수 있다.

그림 11 - Swap 함수의 종료 직전

그런데 왜 main에서는 서로의 값이 바뀌지 않는 것일까?

이는 변수의 정체(메모리 공간의 주소)와 관련이 있다.

 

우선 main 함수에서 인자로 두 변수(val1, val2)를 넘겨 주었다 하더라도

SwapByValue 함수에서의 두 변수(num1, num2)는 에초에 정체가 다른 녀석들이다.

즉, 인자를 전달 받을 때 변수 자체를 넘겨 받는 것이 아니라

다른 함수에서 전달 받을 변수를 따로 선언하여 받게 되는 것이다.

 

따라서 main함수의 변수들과 SwapByValue함수의 변수들은

메모리 공간의 주솟값이 다른 녀석들이라는 소리가 된다.

그렇기 때문에 아무리 SwapByValue를 실행시켜도 main 함수의 변수들은 영향을 받지 않는 것이다.

그림 12 - Call-By-Value의 한계

 

비유하자면 독서실 만료 기간이 다 돼서 2층 취준생 김민수 씨가 연장 신청을 했는데

아르바이트생이 3층 공시생 김민수 씨의 사용 기한을 연장한 것과 같은 맥락이다.

김민수 씨의 만료 기간을 연장하기만 하면 되지만 위치(주소)가 다른 사람의 것을 연장한다면

난감하지않겠는가?

 

우린 왜 main함수의 변수가 영향을 받지 않는지 방금 확인하고 이해했다.

그럼 우린 이제 어떻게 서로의 값을 서로 바꿀 수 있겠는가?

설마 「그 것」 이라고 생각하는가?

 

Call-By-Reference

그렇다!

위의 방법에서 주솟값이 서로 달라 원하는 결과값을 도출하지 못 했다.

그래서 필요한 방식이 바로 'Call-By-Reference' 방식이다.

우선 다음의 코드를 보도록 하자.

int SwapByRef(int* ptr1, int* ptr2)
{
    int temp = *ptr1;
    *ptr1 = *ptr2;
    *ptr2 = temp;
}   // Call-By-Reference

 

SwapByRef 함수에서는 두 개의 주솟값을 인자로 전달받고 있다.

그리고 그 주솟값이 참조하는 영역에 접근하여 저장된 값을 직접 변경하고 있다.

따라서 위 함수를 대상으로 다음의 main 함수를 실행하면

int main()
{
    int val1 = 10;
    int val2 = 20;

    SwapByRef(&val1, &val2);   // 서로의 값이 바뀌길 원함
    cout << val1 << endl;
    cout << val2 << endl;

    return 0;
}

 

당연하게도 val1과 val2의 값이 서로 바뀌어서 출력되는 결과를 확인할 수 있다.

'???: ?'

그림 13 - 필자의 급발진에 이해 못 한 독자

'아 이걸 급발진하네;; 님 혼자 공부하슈? 할거면 똑바로 좀 하라 이말이야.'

걱정하지 않아도 된다.

안 그래도 방금 '마치며' 부분을 적으며 글을 마치려던 참이었다.

이정도로는 우리 높은 평균 수준을 가진 독자들이라면

눈 감고도 해석할 수 있지 않은가?

눈 감으면 어떻게 읽느냐고? 그건 내가 알 바가 아닌 부분이다.

 

그림으로 설명하면 다음과 같다.

그림 14 - (대충 두 변수가 주소를 저렇게 가지고 있다고 가정하는 내용)

main의 두 변수의 내부 데이터와 주소가 저렇다고 하자.

아무튼 일단 저렇다고 치자.

그럼 SwapByRef 함수가 전달 받는 인자는 무엇이겠는가?

그림 15 - 주솟값을 전달 받은 함수

그렇다!

바로 main 함수에서 전달한 주솟값을 받는 것이다!

그럼 이제 SwapByRef 함수의 몸체 실행시켰을 때의 과정은 다음과 같다.

그림 16 - 0x01를 찾아서
그림 17 - val1의 값 변경
그림 18 - ptr2가 가리키는 val2에 10을 저장

자 이렇게 접근 방향과 데이터의 저장을 도식화하여 나타내 보았다.

 

결론?

이상의 개념들을 정리하면 다음과 같다.

  • Call-By-Value
    값에 의한 호출
  • Call-By-Reference
    참조에 의한 호출
  • Call-By-Address
    주소에 의한 호출

'??? Call-by-Address는 또 뭔데 튀어나와;; 그뭔씹!'

사실 이때까지 위 본문에서 서술한 'Call-By-Reference' 방식은 모두

'Call-By-Address' 방식이었다.

그림 19 - 혼란스런 독자

'?????'

이렇게 혼란만 가중될까봐 말 하지 않았지만

혼란만 가중되라고 일부로 지금 말하는 것이다.

Call-By-Reference가 Call-By-Address의 개념을 어느정도 중복하고 있기 때문에

굳이 나누어서 개념을 분리시킬 필요는 없다.

대충 Call-By-Value를 구분할 줄 알면 된다.

값으로 지지고 볶는지 아니면

주소라든가 참조자라던가 다른 것들로 뚝딱뚝딱하던지

세간에서는 굳이 Call-By-Address 방식이라고 명시하지 않는다.

핵심은 함수 외부에 선언된 변수에 접근하는 것이기 때문에

그냥 Call-By-Reference로 퉁치는 것이다.

그래서 다음 시간에 진짜 'Call-By-Reference'를 학습해 보겠다.

 

마치며

이제 우리가 원하는 결과를 얻었으니

오늘 밤엔 두 발 쭉 뻗고 잘 잘 수 있을 것 같은가?

어림도 없다!

우린 아직 참조자의 활용에 대해서 배우지 않았으며

아직 배워야 할 것들이 흘러넘치다 못 해

강과 바다를이루고 젖과 꿀이 넘치는 대지를 형성할 지경이다.

아참.

게임 기획 공부도 해야하고

밴치 마킹도 해야하고

아.


참고 및 출처

  • 윤성우 열혈 C++ 프로그래밍