“In a programming language, an evaluation strategy is a set of rules for evaluating expressions. The term is often used to refer to the more specific notion of a parameter-passing strategy that defines the kind of value that is passed to the function for each parameter (the binding strategy)…”
출처 : WIKIPEDIA
위키백과에 따르면, 평가 전략이란 함수(메서드)의 매개변수를 어떤 방식으로 넘길 것인지 정하는 방식
을 뜻한다.
우리가 흔히 알고있는 평가 전략은 call by value, call by reference와 같은 것들이 있는데,
보통 프로그래밍 언어에 대해 처음 배울 때 등장하는 개념이다.
최근에 자바 언어를 학습하면서, 자바가 call by value와 call by reference 방식을 이용해 매개변수를 전달한다는 글을 많이 보았다. 기본 자료형(char, int, double 등)을 매개변수로 넘길 때는 값 자체를 복사하는 call by value 방식을 사용하고, 참조 자료형을 넘길 때는 값이 아닌 주소를 넘기는 call by reference 방식을 사용한다는 내용이다.
그런데 예전에 파이썬을 공부할 때, 관련 책에서 아래와 같은 설명을 본 적이 있다.
“파이썬은 공유로 호출(call by sharing)하는 매개변수 전달 방식만 지원한다.
이 방식은 루비, 스몰토크, 자바(자바 참조 자료형일 때만 동일하다. 기본 자료형은 값으로 호출(call by value)하는 방식을 사용한다.) 등 대부분의 객체지향 언어에서 사용하는 방식과 동일하다.”
출처 : 전문가를 위한 파이썬 (루시아누 하말류 저)
결국 자바는 call by value와 call by sharing 방식을 이용한다는 것이다. 😮
자연스럽게 아래와 같은 두 가지 의문점이 들었다.
지금부터 이 의문점을 해결하고자 한다.
일단 call by value와 call by reference 방식에 관해 알아보고자 한다.
C언어의 swap 함수를 이용해 두 정수 a와 b의 값을 서로 교환하는 상황에서,
각각의 평가 전략에 따라 결과가 어떻게 다른지 살펴보자.
#include <stdio.h>
void swap(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
int main()
{
int a, b;
a = 10;
b = 20;
printf("before swap : %d %d\n", a, b);
swap(a, b);
printf("after swap : %d %d\n", a, b);
return 0;
}
before swap : 10 20
after swap : 10 20
값
이 복사되어 전달된다.#include <stdio.h>
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a, b;
a = 10;
b = 20;
printf("before swap : %d %d\n", a, b);
swap(&a, &b);
printf("after swap : %d %d\n", a, b);
return 0;
}
before swap : 10 20
after swap : 20 10
주소값
이 전달된다.[참고]
엄밀히 말하면, C언어의 call by reference 방식은 결국 주소값을 넘기는 것이므로, call by value 방식으로 주소값 자체를 복사하여 넘긴다고 보는 것이 맞다. 결국 call by reference 방식을 흉내내는 것이지, 공식적으로 언어 자체에서 지원하는 것은 아니라는 말이다. 하지만 별명에 의해서 원래 값이 변경될 수 있다는 점에서 call by reference 개념을 사용했다고 볼 수 있다.
”Call by reference can be
simulated
in languages that use call by value and don't exactly support call by reference, by making use of references (objects that refer to other objects), such as pointers (objects representing the memory addresses of other objects). Languages such asC
, ML and Rust use this technique.”
출처 : WIKIPEDIA
Call by sharing을 알아보기에 앞서, 아래에 있는 토비님의 글을 꼭 읽어보자. 👍
https://ko-kr.facebook.com/tobyilee/posts/10222585502760852
(물론 직접 읽으면 너무 좋은데) 요약을 하자면,
이쯤에서 “reference values와 call by reference가 뭐가 다르다는 거지?” 라는 의문이 들 수 있다.
call by sharing의 개념에 대해 알아보면 자연스럽게 의문이 해소된다.
루시아누 하말류의 “전문가를 위한 파이썬”에는 다음과 같은 설명이 더 있다.
“공유로 호출한다(call by sharing)는 말은 함수의 각 매개변수가 인수로 전달받은 각 참조의 사본을 받는다는 의미다. 달리 말하면, 함수 안의 매개변수는 실제 인수의 별명이 된다.”
출처 : 전문가를 위한 파이썬 (루시아누 하말류 저)
“결국 call by reference로 주소값이 넘어가면서, a에 x라는 별명이 생긴 것과 같은 개념 아니야?” 라는 생각이 들 수 있는데, 이와는 엄연히 다른 개념이다. 책에 추가적인 설명이 나온다.
“이런 체계의 결과로서, 함수는 인수로 전달받은 모든 가변 객체를 변경할 수 있지만, 객체의 정체성 자체는 변경할 수 없다. 즉, 어떤 객체를 다른 객체로 바꿀 수는 없다.”
출처 : 전문가를 위한 파이썬 (루시아누 하말류 저)
이를 자바 코드를 통해 살펴보면 이해하기 쉽다.
public static void main(String[] args) {
Dog aDog = new Dog("Max");
foo(aDog);
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
reference values
)이 넘어온다.d = new Dog(”Fifi”)
를 통해 d에는 새로운 객체가 할당되었는데, 만약 call by reference 방식이라면 d는 aDog의 별명이므로, main 메서드의 aDog에도 new Dog(”Fifi”)
가 할당되어야 한다.위에 나왔던 루시아누 하말류의 말을 다시 살펴보자. 자바 코드로 봤던 예시가 딱 이 말을 의미한다.
“이런 체계의 결과로서, 함수는 인수로 전달받은 모든
가변 객체를 변경할 수 있지만
, 객체의정체성 자체는 변경할 수 없다
. 즉, 어떤 객체를 다른 객체로 바꿀 수는 없다.”
출처 : 전문가를 위한 파이썬 (루시아누 하말류 저)
💡 결론적으로 call by sharing에 대해 아래와 같이 정리할 수 있다.
추가적으로 토비님을 비롯한 여러 개발자분들은 “매개변수로 전달되는 것도 결국 객체의 주소값을 복사한 값이므로, 결국 자바에는 call by value라는 개념 밖에 없다.”라고 말씀하시기도 한다.
그래서 call by sharing이라는 용어가 공식적이라기 보다는, 저렇게 call by value 방식으로 객체의 주소값의 사본이 전달되어 가변 객체의 변경 사항은 반영되나, 객체 자체를 변경하는 건 안된다는 것을 명시하기 위해서 call by sharing이라는 용어를 붙인 것이라 생각하면 된다. 용어의 차이일 뿐, 개념적으로는 동일한 것을 말한다고 보면 된다. (파이썬과 같이 기본형 자료도 객체로 관리하는 언어에서 설명하기 위해 탄생한 용어라고 한다.)
위키백과에는 사용하는 평가 전략에 따라 언어를 구분한 표가 있는데, 참고삼아 보면 좋을 것 같다.
이제 처음에 들었던 두 가지 의문을 해소할 수 있게 되었다.
아, 마지막으로 혹시나 면접에서 call by value와 call by reference의 차이점이 무엇인지
에 대한 질문이 나온다면, 아래 토비님의 말씀대로 융통성있게 대답해보자. (면접에서 따지지 말자.)
“답변을 할 때, 두 용어를 쓰지 않고 풀어서 하면 됩니다. 원시 타입은 값이 전달 되고 그걸 지지고 볶아도 호출한 쪽이나 다른데 영향을 주지 않습니다. 하지만 오브젝트를 가리키는 레퍼런스 변수를 넘기는 경우는 이를 통해서 호출한 쪽에서 참조하던 오브젝트의 내부 값을 변경할 수 있습니다. 정도?”