6-3-나.참조 호출

다음은 똑같은 동작을 하되 참조 호출 방식으로 변수값을 1 증가시키는 plusref 함수를 작성해 보자. 소스는 다음과 같다.

 

: CallRef

#include <Turboc.h>

 

void plusref(int *a);

 

void main()

{

     int i;

 

     i=5;

     plusref(&i);

     printf("결과=%d\n",i);

}

 

void plusref(int *a)

{

     *a=*a+1;

}

 

앞의 예제와 결과는 같지만 동작하는 방식은 상당히 다르다. plusref 함수의 원형이 바뀌었고 main 함수의 중간 변수 j가 없어졌다. 참조 호출은 실인수의 값을 전달하는 것이 아니라 실인수의 번지를 전달하는 방식이다. &i는 i가 들어 있는 번지인데 이 번지를 plusref 함수로 전달했다. plusref 함수는 이 번지를 a로 받아서 다음 연산을 수행한다.

 

     *a=*a+1;

 

이 연산식에서 *a란 a가 가리키고 있는 번지에 들어 있는 값을 가리킨다. a가 &i를 대입받았으므로 *a는 *(&i)라고 할 수 있으며 *(&i)는 곧 실인수 i와 같다. *a를 1증가시켰으므로 이 연산식에 의해 증가되는 대상은 실인수 i이다. 즉, *a=*a+1; 연산문은 결국 i=i+1;과 같아지는 것이다. 이 함수의 동작을 그림으로 표현하면 다음과 같다.

main에서 i의 번지를 넘겼고 그 번지값을 a로 받아 a가 가리키는 메모리의 값을 1 증가시켰으므로 결국 1증가된 것은 실인수 i이다. 함수 내부에서 포인터를 통해 실인수값을 직접 조작하기 때문에 결과값을 리턴할 필요가 없으며 그래서 plusref 함수는 void형이고 main에서 이 함수의 리턴값을 대입받기 위한 j가 필요없어진 것이다. 두 예제의 1증가시키는 함수들의 동작을 비교해 보자.

 

plusone 함수 : 값을 넘기면 1증가된 값을 리턴한다.

plusref 함수 : 값이 들어있는 번지를 주면 이 번지에 들어있는 값을 1증가시킨다.

 

요컨데 값 호출 방식을 사용하는 plusone 함수는 1더 많은 값을 계산하고 참조 호출 방식을 사용하는 plusref 함수는 실인수를 직접 1증가시킨다. 값 호출이라는 말은 실인수로 값(Value)를 넘긴다는 뜻이고 참조 호출이라는 말은 번지값을 전달받아 실인수를 직접 참조(reference)할 수 있다는 뜻이다.

값 호출과 참조 호출의 또 다른 차이점은 실인수로 상수를 전달할 수 있는가 하는 점이다. 값 호출 방식은 값을 전달하기 때문에 plusone(5)와 같이 상수를 실인수로 사용할 수 있다. i든 k든 5나 3이든 값을 가지기만 하면 형식인수 a가 이 값을 대입받을 수 있다. 심지어 i*k+1같은 수식도 계산된 후에는 값으로 평가되므로 이 값을 전달할 수 있다. 그러나 참조 호출은 번지를 전달하기 때문에 번지를 가지는 변수만 사용할 수 있으며 상수는 사용할 수 없다. 상수는 메모리를 점유하고 있지 않기 때문에 번지가 없다. 즉, 상수는 좌변값이 아니며 참조 호출 함수의 실인수로는 좌변값만 사용할 수 있다.

만약 plusref(&5)와 같이 상수의 번지를 넘기고 싶다고 해 보자. 5라는 상수는 좌변값이 아니므로 &5라는 표현부터가 벌써 잘못된 것이다. 5는 메모리에 저장된 값이 아니므로 번지가 없고 상수 5는 어디까지나 상수 5일 뿐이지 어떤 방법을 쓰더라도 이 값은 6이나 4가 될 수는 없다.

값 호출과 참조 호출 방식에 대해 간단한 예제를 통해 알아보았는데 이 책을 읽는 사람의 57%는 이해를 했을 것이고 43%는 아직도 이해가 안될 것이다. C 입문자에게 참조 호출이라는 개념은 사실 선뜻 이해될만큼 쉽지 않은데 왜냐하면 아직까지도 포인터에 대해 확실히 이해를 못하고 있기 때문이다. 아직도 이해가 가지 않는다면 앞 장으로 돌아가 포인터의 개념에 대해 다시 한 번 더 읽어 보기 바란다. 그리고 다음 예제를 보면 값 호출과 참조 호출에 대해 좀 더 분명하게 이해할 수 있을 것이다.

 

: ValueRef

#include <Turboc.h>

 

void main()

{

     int i,icopy;

     int *pi;

 

     i=5;

     icopy=i;

     icopy=icopy+1;

     printf("i=%d, icopy=%d\n",i,icopy);

 

     i=5;

     pi=&i;

     *pi=*pi+1;

     printf("i=%d, *pi=%d\n",i,*pi);

}

 

이 예제는 변수 i에 들어있는 값을 icopy에 대입한 후에 증가시켜 보고 포인터를 통해서도 증가시켜 보는 실험을 한다. 실인수가 형식 인수로 전달되는 과정은 일종의 대입 연산이므로 이 예제는 함수의 두 가지 호출 방식을 그대로 흉내내고 있다. 실행 결과는 다음과 같다.

 

i=5, icopy=6

i=6, *pi=6

 

먼저 값 호출을 흉내내는 위쪽의 코드를 보자. i는 5로 초기화되었고 icopy에 i 를 대입했으므로 icopy도 5가 될 것이다. 이 상태에서 icopy를 1증가시키면 icopy만 6이 되고 i는 여전히 5의 상태를 유지한다. i값을 대입받은 icopy가 어찌 된다고 해서 i가 영향을 받지는 않으므로 i를 직접 변경하지 않는 한은 i값이 바뀌지 않는 것이 당연하다.

아래쪽 코드는 참조 호출을 흉내내는데 포인터 변수 pi에 i의 번지를 대입한 후 이 번지에 들어 있는 값을 1증가시켰다. pi가 i의 번지를 가리키고 있으므로 pi의 내용을 바꾸면 i가 변경된다. 그래서 i나 *pi나 둘 다 6이 된다. 사실 i와 *pi는 완전히 같은 대상을 가리키므로 같은 값을 가질 수밖에 없다. 만약 이 예제조차도 이해가 가지 않는다면 어쩔 수가 없다. 다음에 포인터에 대해 좀 더 익숙해진 후 참조 호출을 다시 연구해 보기 바란다.

다음은 plusref 함수의 원형에 대한 부연 설명이다. 이 함수의 원형은 plusref(int *a)이고 main에서 이 함수를 호출할 때는 plusref(&i)와 같이 i의 번지를 넘겼다. 인수로 정수형의 포인터(int *)를 요구하므로 이 함수를 호출할 때 정수형 변수의 번지를 전달하는 것이 지극히 당연하다. 하지만 초보자들은 이런 터무니없는 의문을 가지기도 한다. "실인수로 정수형 변수의 번지값을 넘겼는데 받을 때는 왜 *a와 같이 그 번지에 들어있는 값으로 받는가?"라는 것이다. 이런 오해의 시발점은 plusref(int *a) 원형에 있는 *기호를 연산자로 생각하는 것인데 여기서 사용된 *는 "번지의 내용을 읽어라"는 연산자가 아니라 포인터 변수임을 나타내는 구두점이다. 연산자와 구두점을 분명히 구분할 수 있고 int *가 하나의 데이터 타입이라는 것을 이해한다면 이런 오해는 하지 않게 될 것이다.