call by value와 call by reference

2024. 11. 18. 10:12·여러가지 모르는 지식들

두 개념은 함수 호출 시 어떻게 인자를 전달할 것인가에 따라 나뉜다.

함수 호출 시 인자의 값을 복사해서 매개변수에게 전달하면 call by value이다.

인자의 참조나 주소를 매개변수에게 전달하면 call by reference이다.

 

➕ 인자와 매개변수

함수를 정의할 때, 함수명과 반환타입, 그리고 매개변수를 작성한다.

void function(int num){};

이와 같이 정의된 함수는 다음과 같이 호출할 수 있다.

function(10);

10이라는 값을 함수의 매개변수에게 전달하고 있다. 이렇게 10이라는 값과 같이, 함수 호출 시 실제로 넘겨주는 값을 인자라고 부른다.

즉, 함수를 정의할 때 선언하는 변수를 매개변수(formal parameter), 함수를 호출할 때 실제로 넘겨주는 값을 인자(actual parameter)라고 구분짓는다.

 

call by value


call by value 방식은 매개변수에 인자의 값을 복사해서 전달한다.

그렇게 때문에, 매개변수는 복사된 값을 저장하기 위한 메모리 공간을 따로 할당받아 해당 공간에 전달받은 값을 저장한다.

 

이러한 이유로 매개변수와 원본 변수(인자)는 서로 다른 메모리 공간에 존재하게 되고, 이것이 함수 내부에서 매개변수의 값을 변경해도 원본 변수에 영향을 미칠 수 없는 이유이다.

void changeNumberToOne(int num){
    num = 1;
};

int num = 10;
changeNumberToOne(num);
print(num);

실행 결과는 1이 아닌 10이다.

 

call by reference


call by reference 방식은 매개변수에 원본 변수의 참조 혹은 주소를 전달한다. 그러므로 매개변수는 원본 변수의 메모리 주소를 나타낼 수 있는 또 다름 이름(변수)이 된다. 마치 별명과 같다.

 

이것을 다르게 말하면, 매개변수와 인자는 서로 같은 메모리 공간을 나타내고 있다는 것이다.

즉, 함수 내에서 매개 변수를 변경하는 것이 원본 변수에 영향을 미친다는 것을 의미한다.

 

call by reference 방식을 사용할 수 있는 C++ 예시를 보자.

void changeNumberToOne(int& refNum) {
    refNum = 1;
}

int main() {
    int num = 10;
    changeNumberToOne(num);
    print(num);

    return 0;
}

해당 코드의 실행 결과는 1이다. 원본 변수가 매개변수에 의해 변경되었음을 확인할 수 있다.

예시에서 보았듯이, 함수 내부에서 매개변수를 통해 원본 변수를 변경할 수 있으므로, 이로 인해 의도치 않게 코드가 동작할 수 있음에 조심하자.

 

참조값을 전달하는 Java도 call by value?


call by reference는 매개변수에 인자의 주소(참조)를 전달하는 것이다. 그런데 자바는 이 방식을 사용하지 않는다. 자바는 call by value 방식을 사용한다.

이는 자바에서 객체를 인자로 넘겨줄 때 참조값, 즉 객체의 주소를 넘겨준다는 것을 알고 있는 나로써는 당황스러운 사실이었다.

참조값은 주소인데…주소 넘겨주는데… 근데 call by reference가 아니라고?

후에 말하겠지만, 미리 언급하자면 이것은 자바에서의 참조가 진정한 의미의 참조가 아니기 때문이었다. 즉, call by reference에서 언급하는 참조와 자바에서 참조는 분명히 다르다.

 

일단 이해해보자


이를 메모리 관점에서 바라보면 이해가 쉽다. 함수를 호출하는 상황에서 객체가 인자인 상황을 생각해보자.

//main함수

void heyCat(Cat cat){
    //...
}
Cat nabi = new Cat("나비");
heyCat(nabi);

매개변수는 참조값을 전달받아 이를 스택 영역에 저장한다.

call by value 방식에서, 매개변수는 원본 변수와는 다른 메모리 공간을 가지게 된다- 라고 언급한 바 있다. 그림에서 볼 수 있듯이, call by value에 대한 설명과 현재 상황이 일치한다.

 

결국, 자바에서 객체를 인자로 전달한다는 것은 ”값”을 전달한다는 것이므로 call by value 방식을 사용한다는 것이다.

 

또한 call by value 방식에서는 함수 내에서 매개변수를 변경하는 것이 원본 변수에 영향을 미치지 못한다고 설명하였다. 이를 확인해보자.

void heyCat(Cat cat){
    cat = new Cat("애용");
}

이와 같이 매개변수에 새로운 객체의 참조값을 저장해도, nabi 변수는 여전히 기존의 객체에 대한 참조값을 저장하고 있다. 함수 내부에서 매개변수를 이용하여 어떤 짓을 하던, nabi 변수가 다른 객체를 가리키도록 할 수는 없다는 것이다.

void heyCat(Cat cat){
    cat.setName("애용");
}

이것 또한 원본 변수를 변경하는 코드가 아니다. 여기서 변경은 원본 변수가 다른 메모리 주소값을 저장하도록 할 수 있는가- 이기 때문이다.

nabi는 여전히 기존 객체의 주소값을 저장하고 있다. 해당 코드는 단지 객체의 속성값을 변경하였을 뿐이다.

 

참조(주소)를 전달한다는 것의 의미


사실 자바의 참조는 C언어의 포인터 개념과 (거의) 같다. 포인터는 메모리 주소를 저장하는 변수이고, 자바의 참조 변수도 마찬가지이다. 조금 다른 점이 있다면, 자바의 참조 변수는 실제 메모리 주소를 저장하지 않는다는 점이다. (그래서 포인터와 ‘거의’ 같다고 표현했다.)

 

이와 같은 내용을 바탕으로 한다면, C언어 역시 참조를 전달하지 않는 것이 된다. 포인터는 메모리 주소값을 저장하고 있으므로, 이를 인자로 넘기는 것도 call by value인 것이다.

 

정리하자면, 자바의 참조는 C언어의 포인터 개념을 의미한다. 이는 call by reference에서 말하는 참조가 아니다. 이름만 같다고 생각하면 편하다.

 

➕ 사실 자바와 C언어에는 진정한 참조를 수행할 수 있는 기능이 없다. 엄밀히 말하자면 call by reference가 존재할 수가 없는 것이다.

 

call by reference에서 말하는 참조의 개념은 C++ 에서 찾아볼 수 있다.

void changeNumberToOne(int& refNum) { 
    refNum = 1;
}

int main() {
    int num = 10;
    changeNumberToOne(num);
    print(num); // 1 출력

    return 0;
}

위 코드에서 refNum을 참조자라고 부르며, 이를 선언하기 위해서는 변수 타입 뒤에 &(앰퍼샌드)를 붙이면 된다. 이름에서 알 수 있다시피, C++에서는 참조자를 통해 변수를 참조할 수 있다. 참조자를 통해 참조를 구현하고 있다는 말이다.

 

copycode.tistory.com: C++ 강의 7장 - 참조자(Reference)

참조자(reference)는 할당된 하나의 메모리 공간에 다른 이름을 붙이는 것을 말한다. 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름인 것이다. 쉽게 말하면 별명이라고도 할 수 있을 것 같다.

 

참조자는 특정 변수의 실제 이름 대신 사용할 수 있다. refNum은 num 대신 사용할 수 있는 이름이기에, refNum 값을 변경하면 당연히도 num 값도 함께 변경된다. 참조를 전달한다는 것은 이러한 의미인 것이다.

 

참조자를 설명하기 위해 다음과 같은 예시를 들어보겠다. 여기 멍때리는 고양이가 있다. 이 고양이의 이름은 김애용이다.

애용이를 엄마가 나비라는 별명으로 부르고 있다고 가정하자. 이때 엄마가 나비를 살찌운다면, 애용이도 살이 찐 것이다. 누군가 나비를 목욕 시켰다면, 애용이도 목욕을 한 것이다.

 

이는 당연하다. 두 이름 모두 동일한 고양이를 가르키는 이름들이기 때문이다. 이때 나비, 애용이라는 이름은 모두 같은 고양이를 참조하고 있다고 표현할 수 있다.

 

만약 이 고양이를 부르고 싶은 또 다른 별명을 만들고 싶다면, C++에서는 참조자를 하나 더 선언하면 된다.

 

call by reference에서 말하는 것처럼 참조를 매개변수에게 전달하게 된다면, 매개변수는 마치 별명처럼 원본 변수의 또 다른 이름이 된다. 그러므로, 매개변수를 기존 변수의 실제 이름 대신 사용할 수 있게 되는 것이다.

 

 

반면 자바를 이용해서는 고양이를 어떻게 목욕시킬 수 있을까? 자바에는 참조자 개념이 없으니, 나비와 애용이라는 이름은 다음과 같은 뜻을 가질 것이다.

 

나비 = 소파 위에 있는 고양이

애용 = 소파 위에 있는 고양이

 

이제 고양이를 목욕시켜보자. 🚿

내가 알고 있는 것은 고양이의 주소뿐이므로, ‘나비’가 가리키는 고양이를 찾아가서 목욕시키는 방법밖에 없다.

 

나비를 목욕 시켰다 = 소파 위에 있는 고양이를 찾아가서 그 고양이를 목욕시킨다

애용이가 목욕했는 지 확인하는 방법 = 소파 위에 있는 고양이를 찾아갔더니 어 목욕했네

 

고양이를 목욕 시켰다고 해서, 애용이가 소파 위에 있는 고양이라는 사실이 변경되지는 않는다.

이것은 자바의 참조값을 통해 객체의 속성을 변경하는 것과 동일한 느낌이다. 객체의 속성을 변경하는 것이 원본 변수(애용)에 영향을 미치지 못한다라는 것은 이런 것이다.

 

함수를 호출하는 상황을 생각해보면, 우리는 매개변수에게 매번 “소파 위에 있는 고양이”라는 값을 복사해서 넘겨줄 것이다. 이것이 자바가 call by value 방식인 이유이다.

 

reference


https://www.javadude.com/articles/passbyvalue.htm

https://byjus.com/gate/difference-between-call-by-value-and-call-by-reference/

https://www.sololearn.com/en/Discuss/1950236/what-exactly-is-reference-in-context-of-programming

https://copycode.tistory.com/82

'여러가지 모르는 지식들' 카테고리의 다른 글

pinned virtual thread 확인하기  (0) 2025.02.28
pull 했는데 FETCH_HEAD warning: skipped previously applied commit  (0) 2024.04.11
starUML column 안보이는 경우  (0) 2023.10.31
멀티쓰레드  (0) 2023.08.26
intellij 단축키  (0) 2023.07.25
'여러가지 모르는 지식들' 카테고리의 다른 글
  • pinned virtual thread 확인하기
  • pull 했는데 FETCH_HEAD warning: skipped previously applied commit
  • starUML column 안보이는 경우
  • 멀티쓰레드
용쓰개
용쓰개
  • 용쓰개
    용쓰게
    용쓰개
  • 전체
    오늘
    어제
    • 분류 전체보기 (34)
      • Spring (11)
        • 스프링 Data JPA (0)
        • 스프링 시큐리티 (1)
        • QueryDSL (1)
        • webflux (1)
      • JAVA (5)
      • 여러가지 모르는 지식들 (6)
      • 알고리즘 (0)
      • 후기 (0)
        • 일상 (1)
        • 책 (1)
      • 운영체제 (1)
      • 트러블슈팅 (9)
      • 프로젝트 (0)
        • 펫동네 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
용쓰개
call by value와 call by reference
상단으로

티스토리툴바