Programming and my thoughts

* 제 포스팅은 "코딩 인터뷰 퀘스천" 이라는 책을 공부하면서 생각한 바를 정리하고 있습니다. (안하면 까먹어서...ㅠ


파라메터 전달 기법은...

  • Call By Value
  • Call By Result
  • Call By Value-Result
  • Call By Reference
  • Call By Name

이렇게 나와있다. 앞의 Call 대신에 Pass 라고 표기해도 같은 의미이다.

모두 다 이해하면 좋지만... 진하게 표시된 부분만 정확하게 이해하고 넘어가도 무방하다고 생각한다.


1. Call By Value, CODE 1-5

#include <stdio.h>

int plus(int x, int y) {
    return x + y;
}

int main() {
    int i = plus(5, 10);
    printf("%d\n", i);
}
15

공부하기에 앞서... 용어의 혼란을 막기위해 아래 두가지 용어를 배워보자.


> 호출자 함수 (Caller Function)

위 코드에서 main 함수를 보면... plus 함수를 호출합니다.

이 관계를 설명할 때에... main 함수를 호출자 함수라고 하고...


> 피호출자 함수 (Callee Function)

plus 함수를 피호출자 함수라고 한다.


> 실제 매개 변수 (Actual Argument)

main 함수는 plus(5, 10) 이라고 호출하는데...

여기서 plus 에게 전달하는 5, 10 을 실제 매개 변수라고 하고...


> 형식 매개 변수 (Formal Argument)

plus(int x, int y) 에서 x, y 를 형식 매개 변수라고 한다.


Call By Value 는 가장 기본이 되는 파라메터 전달 기법이다.

plus(5, 10) 이라고 하면 5, 10 이 plus(int x, int y) 의 x, y 에 그대로 복사된다. (여기서 5, 10을 상수라고 한다)

여러가지 파라메터 전달 기법들이 많은데... 장단점을 파악하는 것이 중요하다.


단점

  • 매번 메모리 할당이 발생하므로 비효율적이다.
  • 전달하는 값이 아주 큰 경우(객체) 비효율적이다.
  • 객체와 배열을 전달할 경우 많은 메모리를 사용하게 된다.

이렇게 나와있긴한데... 사실 Call By Value 는 가장 많이 쓰인다.

작은 값을 전달해서 함수를 처리하는 경우에는 이만한 방법이 없다.

단점은... 그냥 곰곰히 생각해보면 당연한 말이다.


2. Call By Result, CODE 1-6

// C#, .NET Framework 4.5

// 이 함수는 45 이하의 자연수로 구성된 로또 번호 6 개를 추첨해서 x 에 입력하는 함수이다.
// 굳이 out 을 쓰지않고 return 값으로 x 를 전달하면 되지만...
// Call By Result 예제를 위해 만들어보았다.
public static void GetTodayLuckyLottoNumbers(out int[] x)
{
    int size = 6;
    // 중요한 포인트. 변수 초기화를 함수 내부에서 하고있다.
    x = new int[size];
    var rnd = new Random();
    for (int i = 0; i < size; i++)
    {
        int tmp = rnd.Next() % 45 + 1;
        if (x.Contains(tmp))
        {
            i--;
        }
        else
        {
            x[i] = tmp;
        }
    }
    Array.Sort(x);
}

public static void Main(string[] args)
{
    int[] n;
    GetTodayLuckyLottoNumbers(out n);
    for (int i = 0; i < n.Length; i++)
    {
        Console.Write("{0} ", n[i]);
    }
    Console.WriteLine();
}
1 5 17 27 38 42

그렇게 많이 써보지는 않았는데... 내 경험상으로는... 쓰레드 처리를 하다보면 간혹 쓰기도 했던 것 같다.

Call By Result 를 쓰는 경우... 실제 매개 변수의 값이 형식 매개 변수로 전달되지는 않는다.

일단 GetTodayLuckyLottoNumbers 함수 내부를 살펴보면... 변수 x 초기화를 하고있는 부분이 보인다.

이 과정을 거치기때문에 x 는 GetTodayLuckyLottoNumbers 함수에서 새로운 변수가 된다.

그냥 이런게 있다 생각하면 될 것 같다.

(* 저 로또 번호 조합으로 언젠가 1등 당첨번호가 나올지 궁금하다)


단점

  • 매번 메모리 할당이 발생하므로 비효율적이다.
  • 전달하는 값이 아주 큰 경우(객체) 비효율적이다.
  • 객체와 배열을 전달할 경우 많은 메모리를 사용하게 된다.
  • 실제 매개 변수를 형식 매개 변수의 초기화에 사용할 수 없음.

가볍게 넘어가도 될 것 같다.


3. Call By Value-Result, CODE 1-7


앞서 Call By Result 와는 달리...

피호출자 함수내에서 형식 매개 변수의 초기화 과정이 없어... 전달받은 실제 매개 변수를 그대로 이용할 수 있다.

(* 용어 이해가 어렵다면 다시 포스팅 앞의 용어 부분을 참고하세요)


외계어가 아닌 사람의 언어로 쉽게 말하면...

Call By Value-Result 는 Call By Value 와 Call By Result 의 특징을 모두 가지고 있다.

// c 언어에서는 Call By Value-Result 를 지원하지 않으므로 구현할 수 없다.
// 따라서 그냥 참고만 하자.

#include <stdio.h>

void subtract_x_from_y(int x, int y) {
    x = x - y;
}

int main() {
    int x = 5, y = 10;
    subtract_x_from_y(x, y);
    printf("%d\n", x);
}

위 코드의 실제 실행결과는 5 가 나온다.

만약 이 코드가 Call By Value-Result 로 구현되었다면... 결과는 -5 가 나온다.


아래와 같은 과정이 발생한다.


1) main 함수의 subtract_x_from_y(x, y) 호출

2) subtract_x_from_y 함수내에서 Call By Value 가 발생하여 int x, int y 에 각각 5 와 10 이 복사된다. (Call By Value 발생)

3) subtract_x_from_y 함수내에서 x = x - y 에서 x 의 최종 결과값이 저장된다. (Call By Result 발생)


필자도 한 번도 써본 적이 없어서 이만... 허허

단점을 찾아보니... Call By Value 와 Call By Result 의 단점을 모두 가지고 있다고 한다. (-..-)


4. Call By Reference, CODE 1-8

#include <stdio.h>

// x 와 y 의 값을 바꿔준다.
// 그냥 심심해서 비트 연산으로 만들어봤다.
// temp 변수를 두는 방법에 비해 저장 공간을 덜 쓰지만...
// 코드 가독성이 떨어지므로 이렇게 쓰지는 말자.
void swap(int *x, int *y) {
    *x = *x ^ *y;
    *y = *x ^ *y;
    *x = *x ^ *y;
}

int main() {
    int x = 5, y = 10;
    // x = 5  = 0101
    // y = 10 = 1010
    // *x = *x ^ *y = 0101 XOR 1010 = 1111
    // *y = *x ^ *y = 1111 XOR 1010 = 0101 = 5
    // *x = *x ^ *y = 1111 XOR 0101 = 1010 = 10
    swap(&x, &y);
    printf("x=%d, y=%d", x, y);
}
x=10, y=5

정말 유용하고, 또한 많이 쓰이는 것인데...

현장에서는 수많은 프로그램 오류를 야기시키기도 한다.

바로 단점을 확인하자.


단점

  • 수많은 잠재적인 오류를 야기시킬 수 있다.
  • 프로그램 가독성이 떨어진다.

강력한 방법이지만, 그만큼 많은 문제의 원인이 된다...

이 방법은 호출자 함수에서 전달하는 실제 매개 변수의 변경을 피호출자 함수에서 일으키기 때문이다.


여보시오 의사양반... 이게 뭔 소리인가??? 사람의 언어로 써보면...


* Call By Reference 쉽게 이해하기

1) A 라는 사람이 C 에게 갤럭시 S7 을 한대 건네주기로 했는데... 중간에 B 라는 사람을 통해서 건네주기로 했다.

2) A 가 B 에게 갤럭시 S7 을 준다. 잘 작동하는지 확인해보라고 한다. 

3) B 가 갤럭시 S7 을 뜯어본다. (Call By Reference 발생)

4) B 가 갤럭시 S7 붙어있던 카메라 모듈을 떼서 테스트 한 뒤에 다시 장착하는 것을 까먹고 휴지통에 버린다.

5) C 가 B 로부터 갤럭시 S7 을 받는다.

6) C 가 갤럭시 S7 의 카메라를 사용하고자 하나 될리가 없다.

7) C 가 A 를 찾아가서 멱살을...


쉽게 말해 Call By Reference 를 많이 쓰게되면... 4 와 같은 문제가 발생할 가능성을 잠재적으로 안고가는 것이다.

만약 많은 사람들이 참여하는 프로젝트에서 이런 일이 발생하면... 문제를 해결하는데에 많은 시간이 걸릴 것이다.


그럼 이 과정을 Call By Value 와 비교해보자.


* Call By Value 쉽게 이해하기

1) A 라는 사람이 C 에게 갤럭시 S7 을 한대 건네주기로 했는데... 중간에 B 라는 사람을 통해서 건네주기로 했다.

2) A 가 B 에게 갤럭시 S7 을 준다. 잘 작동하는지 확인해보라고 한다.

3) B 가 전달받은 갤럭시 S7 을 복사기에 넣어서 완전히 동일한 제품을 만든다. (Call By Value 발생)

4) B 는 복사된 갤럭시 S7 의 카메라 모듈을 떼서 테스트 한 뒤에 다시 장착하는 것을 까먹고 휴지통에 버린다.

5) C 가 B 로부터 복사본이 아닌 원본 갤럭시 S7 을 받는다.

6) C 가 갤럭시 S7 의 카메라를 사용한다. 잘 된다. 내가 못생겨서 화가 나지만, 제품은 만족한다.


4 에서 동일한 문제가 있지만 (B 가 카메라 모듈을 분리했다가 다시 조립하는 것을 까먹음)

프로그램의 목적인... "C 가 A 로부터 잘 동작하는 갤럭시 S7 을 받는다" 에는 영향이 없다.


이렇게 Call By Value, Call By Reference 두가지를 이해해보면... 쉽게 까먹지는 않을 것이다.


두번째 단점인 프로그램 가독성이 떨이진다는 특징은... 조금만 생각해보면 쉽게 이해할 수 있다.

기본적으로 Call By Value 에서는 호출자 함수에서 사용하는 실제 매개 변수의 변경이 일어나지않으므로...

매개 변수가 변경되는 걱정을 할 필요가 없어서... 함수의 기능에만 포커스를 잡고 소스를 읽으면 크게 무리가 없다.

그러나... Call By Reference 에서는 피호출자 함수안에서 실제 매개 변수의 변경이 일어나므로...

소스 코드를 분석할 때에... 함수 내부까지 모두 분석할 필요가 있는 것이다.

따라서 가독성이 떨어지게된다.


이론은 이러하나... java, c#, python(v2, v3 모두), javascript 에서는...

시스템 정의 자료형(primitive data type, int, double...)은 Call By Value 로 전달되고...

사용자 정의 자료형(user defined data type, class)은 Call By Reference 로 전달된다.

이런 특징을 잘 파악하도록 하자.


최근의 c# 에서는...

IReadOnlyCollection, IReadOnlyList, IReadonlyDictionary 같은 Interface 가 있는데... 이것들을 이용하면...

함수를 구현할 때에... 호출자에서 쓰이는 매개 변수의 변경을 막고 Readonly 방식으로 변수를 이용할 수 있게된다.


더 나아가... 요즘에는 java, c# 에서도 함수형 프로그래밍의 특징중 하나인 람다 표현식이 유행하는데...

이들을 이용할 때에 Call By Value, Call By Reference 의 특징을 곰곰히 생각해보면 도움이 될 것이다.


5. Call By Name


이건 생략... Algol 에서 쓰인다는데... 필자가 Algol 을 써본 적이 없다.


* C 예제코드는 windows10 x64 환경의 gcc 4.8.1 에서 작성했습니다.

* C# 예제코드는 windows10 x64 환경의 Visual Studio 2012 에서 작성했습니다.