본문 바로가기

주소에 의한 인수 전달 (Passing Arguments by address(Call by Address)) 본문

💘 C++/함수

주소에 의한 인수 전달 (Passing Arguments by address(Call by Address))

Hyonii 2022. 7. 27. 14:25

주소에 의한 인수 전달 (Call by Address)

함수에 변수를 전달할 수 있는 또 다른 방법이 있는데, 주소를 사용하는 것이다.

인수가 주소이기 때문에 함수 매개 변수는 포인터다.

함수는 가리키는 값에 접근하거나 변경하기 위해 포인터를 역참조 할 수 있다.

 

간단한 예제로 살펴보자

 

변수를 하나 선언하고 값과 주소를 찍어보겠다. 그리고 간단한 함수를 만들었다.

이번에는 파라미터가 포인터다!

 

main에서 함수를 호출할 때

 

그냥 value를 넣으면 오류난다.

주소를 보내주어야 하기 때문이다.

 

그러니까

 

이렇게 해서 ptr을 넣어주는 방법이 있다.

포인터를 하나 선언하는 방법이 있다.

그리고

 

바로 주소를 넣는 방법도 있다.

한 가지 더 보여드리자면

 

이렇게 5를 바로 넣으면 안된다.

리터럴이라서 주소가 없기 때문이다.

혹시나 const로 바꾸면 될까? 생각할 수 있다.

 

여전히 안된다.
뒤에 붙여도 안된다.

그냥 주소만 넣어주어야 하는 것이다.

 

그다음 함수 안에서 ptr을 de-referencing 한 것도 찍어보고 ptr도 찍어보고 ptr의 주소도 찍어보자

실행시켜보면

 

처음 나온 것은 숫자 5와 value의 주소다

그다음, foo 함수가 실행이 되면서 그 안에서 포인터가 de-referencing 하니까 5가 나왔고

ptr 값은 위에 주소와 똑같이 나왔고 그 포인터 값의 주소도 나왔다.

함수 두 번 실행한거라 두번 나온 거다.

 

여기서 한 가지 더 찍어보고 싶은 것은

 

이 ptr의 주소가 궁금하니까 한번 더 찍어보자.

얘를 argument로 넣어줬는데 주소가 다르게 나오는 것을 알 수 있다.

 

main에서 출력한 ptr의 주소와 foo 함수에서 나온 ptr의 주소가 다르다

왜냐하면 포인터 변수도 변수이니까!

 

그리고 바로 이전 포스팅에서 int* 를 typedef를 이용해서 교체를 한 적이 있다.

 

이런식으로 하게 되면 그냥 값에의한 전달이다.

결국은 주소라는 값을 그냥 값에 의한 전달을 한 것이다.

결국은 주소 값이 복사가 되고 있을 뿐이다.

포인터 변수도 그냥 변수다! 단지 주소라는 특정한 값을 담고 있을 뿐이다.

 

그다음으로 보여드리고 싶은 것은 const 사용법이다.

 

여기에다 이렇게 const를 넣을 수 있다.

이렇게 되면 de-referencing 한 값을 못 바꾼다.

 

이거 안된다.

반대로 const가 없을 때 

foo 함수를 호출한 다음에 value 변수를 출력하면

 

값이 바뀌어있다.

그래서 이런 식으로 포인터를 사용해서 함수의 출력인 것처럼 사용을 하기도 한다.


이전에 참조에 의한 전달 설명한 것과 비슷하다. (↓ degrees, cos, sin 예제)

 

참조에 의한 인수 전달 (Passing Arguments by Reference(Call by Reference))

참조에 의한 인수 전달 (Call by Reference) 값으로 전달은 두 가지 한계가 있다. 첫째, 큰 구조체 또는 클래스를 함수에 전달할 때 값으로 전달은 인수의 복사본을 함수 매개 변수로 만든다. 이 경우

hyoniidaaa.tistory.com

이전 포스팅에서는 레퍼런스를 이용했었는데 이번에는 포인터를 사용해보자.

 

여기서는 참조에 의한 호출과는 다른것이
앞에 &를 붙여서 주소로 바꿔서 넣어주어야 한다.

조금 더 번거롭다.

출력해보면

 

함수에서 넣어준 값이 그대로 밖에서도 영향을 주어서 반영된 것을 볼 수 있다.

C 프로그래밍할 때 이런 형태의 코딩을 아주 많이 연습한 분들도 계신다.

그래서 좀 더 헷갈리는 면이 있는 것이다.

실용적인 면에서는 레퍼런스 쓰는 게 훨씬 편하고 좋다.

 

이때 헷갈릴 수 있는 부분이 있다.

 

얘를 이렇게 넣어주었더니
함수에서 값이 바뀌니까 같이 바뀌었다.

레퍼런스로 전달된 것과 같은 효과가 있으니까 레퍼런싱인 것처럼 머릿속에서 착각할 수 있다.

같은 변수가 넘어온 것이라고 착각할 수 있는데 전혀 아니다.

그래서 주소 찍어서 확인해봤던 것이다.

 

이 둘의 주소는 다르다.

결론은 내부적으로는 값에 의한 전달이다. 

함수에 포인터를 전달하면 포인터의 값(주소)이 인수에서 함수의 매개 변수로 복사된다.

함수 매개 변수의 값을 변경하면 복사본만 변경된다.

따라서 원래 포인터 인수는 변경되지 않는다.

 

주소 자체가 값에 의한 전달이 되더라도 여전히 그 주소를 역참조하여 인수의 값을 변경할 수도 있다.

  • 인수를 주소로 전달할 때 함수 매개 변수는 인수에서 주소의 복사본을 받는다.
    이 시점에서 함수 매개 변수와 인수는 모두 같은 값을 가리킨다.
  • 함수 매개 변수가 가리키는 값을 변경하기 위해 역참조 하면
    함수 매개 변수와 인수가 모두 같은 값을 가리키기 때문에 인수가 가리키는 값에 영향을 준다.
  • 함수 매개 변수가 다른 주소로 지정되면 함수 매개 변수가 복사본이기 때문에 인수에 영향을 미치지 않으므로
    복사본을 변경해도 원본에 영향을 미치지 않는다.
    함수 매개 변수의 주소를 변경한 후에는 함수 매개 변수와 인수가 다른 값을 가리키므로
    역참조하더라도 더는 인수가 가리키는 값에 영향을 미치지 않는다.

const 주소로 전달 (Passing by const address)

그렇다면 과연 포인터를 사용해서 전달하는 게 쓸모가 없는 건가? 싶지만 그렇지 않다.

많이 사용되는 C 스타일 코딩이

 

이런 식으로 안에서 작업을 하는 방식이다. 그리고

 

이런식으로 값을 바꾸면

foo 함수에 입력으로 들어온 array도 값이 같이 변한다.

왜냐하면 메모리 주소를 찾아가서 거기에 있는 값을 바꾸는 것이기 때문이다.

 

그리고 const를 넣을 때의 용법에 대해서 말씀드리려고 한다.

 

const가 들어가면 바로 에러가 뜬다.
이 대괄호 기호가 사실은 de-referencing 이라는 점 알고 계실거라 생각한다.

혹시 의아하다면 다른 변수를 한 개 더 넣어보자.

 

여기서도 마찬가지로 const로 막아버리면
안된다.

그리고 한 가지 더 보여드리자면

 

int x = 1; 이라고 하고

ptr의 주소 자체를 바꾸는 것이 가능하다.

이걸 안되게 막으려고 하면 어떻게 해야 될까?

 

여기다가 const를 넣어버리면 못바꾼다.

pointer와 const에서 설명드린 적 있다. const 위치 차이

 

포인터와 const (Pointer and Const)

Pointing to const variables 일반 변수에 const를 사용해서 상수로 만들 수 있듯이 포인터에도 const를 사용할 수 있다. 그런데 일반변수와는 조금 다르다. 포인터와 const가 어떻게 사용되는지 설명드리겠

hyoniidaaa.tistory.com

이렇게 막아버릴 수 있는데 굳이 이렇게 안 쓰는 경우가 많다.

왜냐하면 얘는 결국 값에 의한 전달이고

 

결국 ptr은 이 영역에서 local variable인 것처럼 작동을 하기 때문이다.

ptr 값 자체를 바꾸는 것은 함수 밖(함수를 호출하는 곳) 입장에서 봤을 때에는

크게 해가 될 것이 없다고 보기 때문이다.

 

그런데 간혹 ptr[0]… 어쩌고 해서 구현할 때 실수할 확률이 높아질 수 있다.

중간에 주소를 바꾸는 것은 잘 쓰는 방식이 아니다.

 

그래서 간혹 오픈소스를 보시면 이렇게 const가 두 개 있는 구조도 종종 볼 수 있다.

 


주소로 전달의 장단점 (Pros and Cons of pass by address)

주소로 전달의 장점:

  • 주소로 전달은 함수가 인수 값을 변경할 수 있으므로 유용하다.
  • 인수의 복사본이 만들어지지 않으므로 구조체 또는 클래스와 함께 사용하는 경우에 빠르다.

주소로 전달의 단점:

  • 리터럴과 표현식은 주소가 없으므로 사용할 수 없다.
  • 모든 값은 null인지 확인해야 한다. null 값을 역참조 하려고 하면 충돌이 발생한다.
  • 포인터 역참조는 값에 직접 접근하는 것보다 더 느리다.

주소로 전달을 사용해야 하는 경우:

  • 내장 배열을 전달할 때
  • 포인터를 전달할 때

주소로 전달을 사용하지 않아야 하는 경우:

  • 구조체 또는 클래스를 전달할 때 (참조로 전달 사용)
  • 기본 자료형의 값을 전달할 때 (값으로 전달 사용)

주소로 전달과 참조로 전달의 장단점은 거의 같다.

그러나 참조로 전달하는 것이 주소로 전달하는 것보다 더 안전하므로 대부분의 경우 참조로 전달하는 게 좋다.

 

Comments