프로그래밍 언어/[ C ]

[ C ] 00. 함수 호출방식

kim.svadoz 2020. 8. 13. 09:55
728x90
반응형

[ 함수 호출방식 ]


Call-by-value

값에 의한 호출

  • 함수가 호출될 때, 메모리 공간 안에서는 함수를 위한 별도의 임시 공간이 생성된다.(c++의 경우 stack frame) => 함수가 종료되면 해당 공간은 사라진다.
  • 스택 프레임(Stack Frame) : 함수 호출시 할당되는 메모리 블록(지역변수의 선언으로 인해 할당되는 메모리 블록)
  • call-by-value 값에 의한 호출방식은 함수 호출 시 전달되는 변수의 값을 복사하여 함수의 인자로 전달한다.
  • 복사된 인자는 함수 안에서 지역적으로 사용되는 local value의 특성을 가진다.
  • 따라서 함수 안에서 인자의 값이 변경되어도, 외부의 변수의 값은 변경되지 않는다.!!
  • 장점 : 복사하여 처리하기 때문에 안전하다. 원래의 값이 보존이 된다.
  • 단점 : 복사를 하기 때문에 메모리 사용량이 늘어난다.

- 예제

#include <stdio.h>

void swap(int num1, int num2){
    int temp = num1;
    num1 = num2;
    num2 = temp;
}
void main(){
    int a = 20, b = 60;
    swap(a, b);
    printf("a: %d, b: %d", a, b);
}

결과=> a: 20, b: 60

swap()에서 값만 받아와서 내부적으로 처리를 하고 아무 것도 넘기지를 않는다. 변수를 주소로 가져오거나 포인터로 통해서 가져온 것이 아니기 때문에, 새로운 변수를 만들어서 값을 대입해서 처리한 것이다. 이 경우 교체는 되지 않고 swap() 내부에서만 처리가 된다. 반환형이 없기에 사실상 위에서는 의미 없는 행동을 했다. 만일 swap()이 아닌 다른 함수로 리턴 값을 넣었다면, 안정적으로 처리를 해서 결과를 도출해준다. 하지만 swap()의 경우 이런 방법은 잘못된 사용방법이다.

Call-by-reference

참조에 의한 호출

  • 함수가 호출될 때, 메모리 공간 안에서는 함수를 위한 별도의 임시 공간이 생성된다.
  • call-by-reference 참조에 의한 호출방식은 함수 호출시 인자로 전달되는 변수의 레퍼런스를 전달한다.(해당 변수를 가리킨다.)
  • 따라서 함수 안에서 인자의 값이 변경되면, 아규먼트로 전달된 객체의 값도 변경된다.
  • 장점 : 복사하지 않고 직점 참조를 하기에 빠르다.
  • 단점 : 직접 참조를 하기 위해 원라 깂이 영향을 받는다.(리스크)

- 예제

#include <stdio.h>

void swap(int &num1, int &num2){
    int temp = num1;
    num1 = num2;
    num2 = temp;
}
void main(){
    int a = 20, b = 60;
    swap(a, b);
    printf("a: %d, b: %d", a, b);
}

결과=> a: 60, b: 20

swap()에서 main()의 a,b 주소를 가져와서 처리를 했다. 직접 주소 가져와서 처리를 했기 때문에 swap()의 내부 처리로도 a,b가 교체가 되었다. 이렇게 보듯 단점으로는 주소나 포인터를 사용하면 직접 변수에 접근하기 때문에 리스크가 있다.

이러한 개념을 이해하고 함수의 필요한 ㄷ데이터의 상관 관계를 이해하여 만들면 효율적으로 만들수가 있겠다.

더블포인터 사용하기

"call_by_value()는 값을 넘긴 것이고, call_by_refer()도 값을 넘긴 것이다. 다만, call_by_value()에서는 int형 값을 넘긴 것이고, call_by_refer()에서는 int 주소형 값을 넘긴 것이다."

포인터가 어떤 변수의 주소값을 가리킨다라고 하면, 이중포인터는 포인터 변수의 주소값을 가리킨다고 할 수 있다.

즉, 둘 다 변수의 주소값을 가리키는 것이다.

다만 포인터 변수는 이미 포인터이다보니, C언어로 표현할 때에 포인터 표시가 2개 이상 연속으로 된다는 것일 뿐입니다.

- 예제

int global_val = 30;

void call_by_value(int *val){
    val = &global_val;
}
void call_by_refer(int **ref){
    *ref = &global_val;
}
int main(){
    int local_val = 10;
    int *value = &local_val;
    int *refer = &local_val;

    printf("before : *value=%d, *refer=%d\n", *value, *refer);
    call_by_value(value);
    call_by_refer(&refer);
    printf("after : *value=%d, *refer=%d\n", *value, *refer);
}

main()시작시에 두 변수 모두 동일한 local_val의 주소값을 가리키게 했으니, *value, *refer는 10이 된다.

그리고 아까 전과 동일하게 call_by_value()는 변수를 그대로, call_by_refer()는 변수의 주소값을 인자로 보내고 각 함수에서는 global_val의 주소값을 넣어주었다.

# 결과
before : *value=10, *refer=10
after : *value=10, *refer=30
  • call_by_value()에서의 val은 &global_val을 받긴 하지만 함수를 종료하고 나면 값은 사라진다. 그러므로 main()에서는 여전히 local_val을 가리키고 있으며 10이 출력이 된다.
  • call_by_refer()에서의 ref는 포인터변수의 주소값이므로, *ref에 &global_val을 넣게 되면 main()에서의 refer값이 변경되는 셈이므로, local_val이 아니라 global_val을 가리키고 있으며, 30이 출력된다.

위에서 언급했듯이, 값도 값이고 주소값도 값이다. 중요한 것은 내가 담고자 하는 변수의 주소값이 필요한 것인데, 그 변수가 포인터 변수인 것이다.!

결과적으로 내가 담고자 하는 (포인터)변수의 주소값이 필요한 것이 된다.

main()에서 전달하는 변수가 포인터변수였고, 그 포인터변수의 주소값을 넘겨야 하므로, &refer가 되어야 하는게 맞으며 그에 맞춰서 call_by_refer(int **ref)처럼 이중포인터가 되는게 맞다!

728x90
반응형