F.R.I.D.A.Y.

CallByValue vs CallByReference 본문

DEV/C C++

CallByValue vs CallByReference

F.R.I.D.A.Y. 2020. 1. 9. 22:41
반응형

 이번 시간에는 함수에 인자를 넘길 때 넘기는 방식에 대해 이야기해봅니다.


시작에 앞서

 이번 포스트에서는 아래 코드를 이용해 예시를 들도록 하겠습니다.

#include <stdio.h>

void printA(int a){
    printf("%d\n", a);
}

void printAbsA(int a){
    printf("%d\n", a > 0 ? a : -a);
}

int main(void){
    int a;
    
    scanf("%d", &a);
    
    printA(a);
    printAbsA(a);
    
    
    return 0;
}

값을 복사해서 넘기기 : CallByValue

 대표적으로 C언어가 추구하는 방식입니다. 한국어로는 값에 의한 전달 모든 파라미터는 값의 복사. 즉, 다른 공간에 같은 값을 생성해 전달합니다. 일단 전달이 이루어지면 전달하는 데이터의 원본 값이 변경되어도 전달된 값에는 영향을 미치지 않습니다[# 달리 이야기하면 두 값은 동기화되지 않는 값이라고 생각할 수 있습니다.].

 이러한 방식은 어떤 값을 동시 사용할 때 값의 오염이 이뤄지지 않는다는 장점을 가지고 있지만, 반대로 하면 어떤 공간에서 다른 공간을 동적으로 접근할 수 없다는 문제가 발생합니다. 이 문제를 C언어에서는 포인터 문법[# C언어에서 제공하는 포인터 문법은 언어적에서 임의로 만든 기술이 아닌 CPU에서 사용하는 원래의 기술을 개발자가 사용할 수 있도록 제한을 풀어둔 것입니다. 물론 언어 수준에서 몇 가지 기능이 추가되더라도 기본적으로 포인터는 기계어에서도 사용할 수 있는 기술입니다.]을 이용해 타파하고 있습니다.

 

 


공간을 공유해 넘기기 : CallByReference

 C++에서, 혹은 Python, Javascript 등 일부 언어에서 객체를 전달할 때 사용되는 문법입니다. Javascript를 기준으로 설명하면 아래 코드를 보겠습니다.

// Javascript

var obj = {a:4, b:10};

console.log(obj);

function change(t){
    var temp = t.a;
    t.a = t.b;
    t.b = temp;
}

change(obj);

console.log(obj);

 change라는 함수가 존재합니다. 이 함수는 객체의 프로퍼티의 두 값을 변경하는 역할을 담당합니다.

 

 처음 console.log(obj)를 통해 obj 객체를 출력하면 이미지와 같은 결과를 도출할 것입니다.

 그러나 change 함수를 이용한 후 obj 객체를 출력하게 되면 a와 b의 값이 변경되어있음을 알 수 있습니다.

 이와 같이 함수의 파라미터로 넘기는 값을 새로 만들지 않고 값이 들어있는 공간 자체를 넘기는 방식을 참조에 의한 전달[#Eng CallByReference]이라고 합니다.

 

 이 방식의 장점은 임의의 함수에서 값을 사용하고 그 값을 반환해야 할 때 원본 데이터 및 공간을 이용했으므로 반환할 필요가 없이 결과 값이 해당 공간에 이미 존재한다는 점입니다. 역으로 값에 의한 전달에서는 장점으로 작용했던 값의 오염 방지가 안되어 개발자가 직접 그 기술을 구현해주어야 합니다.

 

 

 

 C언어 코드와 python 코드의 실행 값을 비교해보겠습니다.

#include <stdio.h>

int main(void){
    int a,b;
    a = 1;
    b = 1;
    
    if(&a == &b) printf("a변수 값의 위치와 b 변수 값의 위치는 동일합니다.\n");
    else printf("a변수 값의 위치와 b변수 값의 위치는 서로 다릅니다.\n");
    
	return 0;
}
# python
a = 1
b = 1
if(id(a) == id(b)):
    print("a변수 값의 위치와 b 변수 값의 위치는 동일합니다.")
else:
    print("a변수 값의 위치와 b변수 값의 위치는 서로 다릅니다.")

goorm ide.
python 3.8.1

 동일한 기능을 수행하는 코드이니 동일한 결과가 도출될 것처럼 보이지만, 언어 수준에서 추구하는 방향이 다르다 보니 서로 다른 결과가 도출되었습니다.

 python의 경우 더욱 특별한데 모든 값[# 상수, 객체 등]이 객체로 만들어집니다. 그래서 상수가 메모리에 상주합니다.

 

 어떻게 작성했느냐보다는 언어를 얼마나 잘 이해했는지가 관건인 문제입니다. 개발자가 하나의 언어로만 살아갈 수는 없습니다. 무조건 못해도 2-3개의 언어는 배워갈 텐데, 언어가 추구하는 방향들을 잘 이해하고 있어야 원하는 기능을 빙빙 돌지 않고 제 때 만들 수 있습니다.

 

더보기

# 얕은 복사? 깊은 복사?

 객체를 다루는 프로그래밍을 하다 보면, 얕은 복사로 인해 발생하는 문제들이 있습니다. 얕은 복사와 깊은 복사는 무엇일까요?

괄호 안의 숫자는 주소의 16진 표기

 A라는 공간에 10이라는 값이 들어있다고 생각해봅시다.

 얕은 복사(A')는 이렇습니다. 원본 A의 주소를 복사해 값으로 가집니다. 이때 사용할 때는 원본 데이터를 사용하며, 일반 변수처럼 사용할 수 있습니다. 그러나 A'의 값을 변경하면 A의 값도 함께 변경됩니다.

 깊은 복사(A")는 이렇습니다. 새로운 공간을 만듭니다. 그리고 그 공간에 원본(A)이 가지고 있는 값을 복사해 자신의 값으로 가집니다. 따라서 A"의 값을 변경하더라도 이미 원본으로부터 분리된 값(공간)이기에 원본(A)의 값에는 영향을 미치지 않습니다.

 

얕은 복사(좌), 원본(중앙), 깊은 복사(우)

주소에 의한 전달? CallByAddress

 흔히 포인터 문법을 이용해 값을 넘기는 방식을 주소에 의한 전달[# CallByAddress]이라 칭하곤 합니다. 정확히 말하면 주소에 의한 전달은 값에 의한 전달입니다.

 간섭할 수 없는 공간의 주소를 알고 있어서 연산자를 활용해 간접적으로 영향을 줄 수 있어서 간접 참조 연산자(*)라 불리는 이 방식이 값에 의한 전달이라기엔 그렇고, 그렇다고 참조에 의한 전달이라기에도 그 기반을 값에 의한 전달로 두고 있기 때문에 아예 새로운 용어로 CallByAddress라 나온 것이 아닌가 싶습니다.

 다시 한번 말하지만

CallByAddress ∈ CallByValue[# CallByAddress는 CallByValue에 포함된다.]

 입니다.

 

# index

728x90
반응형

'DEV > C C++' 카테고리의 다른 글

배열(array) part1. default  (1) 2020.01.19
포인터(pointer) part2. 다차원 포인터  (0) 2020.01.17
함수 호출 구조  (0) 2020.01.07
포인터 part1. default  (0) 2020.01.07
배열 변수의 이름이 0번 인덱스의 시작 주소인 이유  (0) 2020.01.06
Comments