본문 바로가기
develop

함수호출시의 이중포인터 사용하는 이유

by 찬이 2014. 11. 21.

written by chanywa, 2014-11-21
blog : http://chanywa.com
e-mail : chany@chanywa.com

"포인터도 머리 아픈데, 이중포인터는 또 뭐야... ㅠ.ㅠ"

이런 생각을 하는 분들께, 개념정리하는데 도움이 되지 않을까 하는 생각에 몇자 적어봅니다.

저는 사실 '이중포인터' 라는 말은 의미가 없다고 생각합니다.
포인터의 포인터라는 뜻으로 이중포인터라고 이름을 붙인 것 같은데, 실제로는 그냥 똑같은 포인터입니다.
그렇다면, 왜 이중포인터가 존재하며, 이것을 사용하느냐라는 문제를 고민해볼 수 있을 것입니다.

이중포인터는 특별한 용법이나 카테고리라고 한정하기는 힘들기 때문에,
이 글에서는 그 중에서도 서브함수 호출시의 이중포인터 사용에 대해서 잠시 살펴보도록 하겠습니다.

 

 

call by value, call by reference

이중포인터의 존재이유를 살펴보기전에, 먼저 생각해볼 것이 있습니다.
혹시 C 공부를 하면서 "call by value, call by reference" 라는 말을 들어보신 적이 있나요.
제 생각에는 이 개념을 먼저 짚고 넘어가는게 좋을 듯 합니다.
아래 소스를 한번 보도록 하겠습니다.

 

void call_by_value(int val)
{
        val = 20;
}

void call_by_refer(int *ref)
{
        *ref = 20;
}

int main()
{
        int value = 10;
        int refer = 10;

        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);
}

 

이런 류의 프로그램을 한번쯤 봤다 싶은 분들이 많으실 겁니다.

 

간단히 프로그램을 살펴보겠습니다.

value, refer 변수 둘다 초기값은 10 입니다.
이 동일한 값을 가진 2개의 변수를 서브함수의 인자로 전달하고, 서브함수내에서 다른 값으로 변경한 후의 상태를 확인해보기 위한 것입니다.
두 함수의 차이가 있다면 call_by_value()는 int 변수값을 그대로 전달하고, call_by_refer()는 int 변수의 주소값을 전달합니다.
만약 두 경우의 차이를 모르신다면, 둘 다 20으로 변경된다고 생각하실 것이고, 차이를 아신다면 refer 만 20으로 변경된다고 생각하시겠지요.

 

실행결과는 이렇습니다.

before : value=10, refer=10
after  : value=10, refer=20

 

이 경우의 차이를 잘 모르겠다 싶으시면, 아래 내용을 읽어봐주세요.

 

함수를 호출할 때 전달하는 인자는 변수 자체를 전달하는 것이 아니라, 변수에 저장된 값을 전달합니다. 그리고 서브함수 내에서 별도의 변수를 만들어 값을 담아서 사용하게 되죠..
즉, main()에서의 value와 call_by_value()의 val, main()에서의 refer와 call_by_refer()에서의 ref 변수는 서로 다른 메모리에 존재하는, 사실상 각각 별개의 변수로서 존재하게 됩니다.
그렇기 때문에, call_by_value() 에서 val = 20; 을 실행하더라도 main()에서의 value는 변경되지 않습니다. 그리고 함수가 종료되면서 val는 사라지고, main()로 되돌아왔을때의 value는 여전히 10인 상태입니다.

 

그러면, call_by_refer() 에서 *ref = 20; 을 실행했을 때는 왜 refer에 20이 저장이 되었을까요.

그 이유는 바로, ref 라는 변수에다 저장을 한 것이 아니라, *ref 즉 ref가 가지고 있는 주소값 위치에 20을 저장했기 때문입니다. 왜냐하면 ref는 &refer였으므로 refer의 주소를 가지고 있는데, *ref는 ref가 가지고 있는 주소에 해당하는 메모리의 값을 뜻하기 때문에, refer의 값을 의미합니다.

그러므로, call_by_refer(&refer)를 실행함으로써 main()에서의 refer를 전달받은 것이 아니라, refer 변수의 주소값을 전달받은 것이고, 그 주소값에 해당하는 위치에다 20 이란 값을 저장하게 되니, *ref = 20 은 ref 에 저장하는 것이 아닌, refer에 저장하는 것이 되는 셈입니다.

 

 

double pointer = pointer ?

이중포인터 얘기로 넘어가기전에 한가지만 더 생각해보고 가시죠.

"call_by_refer()와 call_by_value()에 대해서 얘기를 할때, call_by_refer()는 주소값을 넘긴 것이고, call_by_value()는 변수값을 넘긴 것이다."

이 말에 대해서 동의하시나요?
위에서 한창 언급했던 내용들을 이해하셨다면, 아마도 이 말에도 동의하실 것입니다.

 

그럼, 이번에는 여기서 조금만 말을 바꾸어, 다르게 생각을 해보겠습니다.

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

이 말에 대해서는 어떻게 생각하세요?
저는 이전 글과 같은 뜻이라고 생각되는데요, 여러분들도 그렇게 생각되시려나요.

C 문법상으로는 포인터 변수와, 일반 변수를 구분을 하지만, 사실상 그냥 다 변수이고, 다 값입니다.
실제 메모리상에서는 주소와 값이 구분되는 것도 아니고, 2진수와 16진수, 10진수가 구분되는 것도 아닙니다.
그러므로 두 글은 동일한 뜻이긴 합니다만, 저는 후자의 경우가 더 정확한 의미라고 생각합니다.
특히 이중포인터의 이해를 돕고자 하는 측면에서는 더욱 말입니다.

그럼, 이 말을 기억한 채로 이중포인터로 넘어가보겠습니다.

 

 

double pointer !!

포인터의 포인터, 포인터 기호가 2개(혹은 그 이상)인 이중포인터.
"이것도 그냥 포인터일 뿐인데, 이걸 언제, 왜 써야 하나? 머리아프게~"

그런데요, 이 글을 앞부분에서 말씀드렸다시피, 이중포인터나 포인터나 거의 같은 말입니다.
포인터가 어떤 변수의 주소값을 가리킨다라고 하면, 이중포인터는 포인터 변수의 주소값을 가리킨다고 할 수 있습니다.
즉, 둘다 변수의 주소값을 가리키는 것입니다.
다만 포인터 변수는 이미 포인터이다보니, C언어로 표현할 때에 포인터 표시가 2개 이상 연속으로 된다는 것일 뿐입니다.

 

이중포인터의 사용이유에 대한 이해를 돕기 위해, 앞서 봤던 예제 프로그램을 약간만 수정해보기로 하겠습니다.
변수를 포인터 변수로 변경해보는 정도만 해봅니다.

int형 변수였던 value, refer가 이번에는 int 포인터형 변수입니다.
포인터형 변수이니 상수를 직접 넣기보다는 다른 변수를 가리키도록 수정했습니다.

 

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이 출력됩니다.

 

value가 주소값이므로 안사라지는게 맞지 않냐고 생각하는 분도 계시겠지요.
제가 위에서 언급한 적이 있듯이, 값도 값이고 주소값도 값입니다.
중요한 것은 '내가 담고자 하는 변수의 주소값'이 필요한 것인데, 그 변수가 포인터 변수란 말이죠.
그러면 이렇게 정리됩니다.
'내가 담고자 하는 (포인터)변수의 주소값'이 필요한 것이 됩니다.
앞부분에서 여러 차례 언급했었습니다. int형 변수, int 포인터형 변수... 둘다 같은 변수일 뿐이라고 말이죠.

main() 에서 전달하는 변수가 포인터변수였고, 그 포인터변수의 주소값을 넘겨야 하므로, &refer가 되어야 하는 게 맞으며, 그에 맞춰서 call_by_refer(int **ref) 처럼 이중포인터가 되는게 맞겠지요.
아직 좀 헷갈리실 수 있는데, 제가 언급한 몇가지 핵심 위주로 다시 한번 글을 읽어보시길 권해드립니다.

 

 

bonus~

제가 '이중포인터나 포인터나 거의 같다', '변수나 포인터 변수나 같은 변수이다'라는 식의 말들을 언급했었습니다.
아직 의아해 하시는 분들을 위해서 보너스 삼아 한가지 예제를 더 보여드리겠습니다.

직전에 사용된 이중포인터를 사용하는 예제 소스가 있었죠?
이것을 사실 단일포인터만을 사용하여 call by reference 방식으로 사용하는 것입니다.

 

int global_val = 30;

void call_by_value(int *val)
{
    val = &global_val;
}

void call_by_refer(int *ref)
{
    *ref = (int)&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((int*)&refer);
	printf("after  : *value=%d, *refer=%d\n", *value, *refer);
}

 

call_by_refer(int *ref) 입니다. 하지만, 결과는 올바르게 나옵니다.

 

before : *value=10, *refer=10
after  : *value=10, *refer=30

 

call_by_refer((int*)&refer); 에서 refer가 포인터변수이므로, &refer는 이중포인터인 셈입니다.
하지만 이것을 (int*)로 형변환(casting)합니다.
왜냐하면, 실제로는 이중포인터란 것은 존재하지 않기 때문입니다.
그러면 call_by_refer()에는 refer의 주소값이 int*형 변수로서 ref에 전달되고,
ref = &global_val; 을 하게 되면 main()의 refer에 담긴 주소값이 변경되는 것입니다.

 

물론 좋은 방법은 아닙니다만, 장난삼아서 해본다면
아예 이런 방법으로도 가능합니다.

 

int global_val = 30;

void call_by_value(int *val)
{
    val = &global_val;
}

void call_by_refer(int ref)
{
    *(int*)ref = (int)&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((int)&refer);
	printf("after  : *value=%d, *refer=%d\n", *value, *refer);
}

 

int형 변수로 전달받았음에도 처리가 가능하죠.
왜냐하면 몇 번이나 언급했듯이,
int 변수나 int* 변수나 다 같은 변수이고, 포인터나 이중포인터나 다 같은 포인터이기 때문입니다.

 

 

^^)/

너무나 간만에 적는 C 관련 포스팅이라, 지루하진 않으셨나 모르겠습니다.
잘못된 내용이나 오타는 지적해주시면 수정하도록 하겠습니다.
그리고 부끄럽지만, 출처를 밝히시면 글을 퍼 가셔도 좋습니다.

두서 없는 글, 읽어주셔서 감사합니다.

댓글