이번 강좌에서는

  • 포인터에 대한 완벽한 이해
  • *, & 단항 연산자의 의미


  우왕~ 안녕하세요 여러분. 아마 C 언어를 배웠거나 배우고 있는 사람들은 포인터에 대해 익히 들어 보셨을 것 입니다. 이해하기 힘들기로 악명 높은 그 포인터를 말이죠. 하지만, 저와 함께 한다면 큰 무리 없이 배우 실 수 있을 것이라 생각됩니다.


  포인터를 이해하기 앞서
 

  앞서 3 강에서 이야기 하였지만 모든 데이터들은 메모리 상에 특정한 공간에 저장 되어 있습니다. 우리는 앞으로 편의를 위해, 메모리의 특정한 공간을 '방' 이라고 하겠습니다. 즉, 각 방에는 데이터들이 들어가게 되는 것 입니다. 보통 사람들은 한 방의 크기를 1 바이트 라고 생각합니다. 우리가 만약 int 형 변수를 정의한다면 4 바이트 이므로 메모리 상의 4 칸을 차지하게 됩니다. 그런데 말이죠. 프로그램 작동 시 컴퓨터는 각 방에 있는 데이터를 필요로 하게 됩니다. 따라서, 서로 구분하기 위해 각 방에 고유의 '주소(address)' 를 붙여 주었습니다. 우리가 아파트에서 각 집들을 호수로 구분하는 것 처럼 말입니다. 예를 들어 우리가 아래와 같은 int 변수 a 를 정의하였다면 특정한 방에 아래 그림 처럼 변수 a 가 정의됩니다.

int a = 123; // 메모리 4 칸을 차지하게 한다.



메모리에 123 이란 수가 있고 이 수는 메모리에 0x152839 (앞에 0x 는 이 수가 16 진수로 표시되었다는 것을 의미해요) 에 위치



이 때, 0x152839 는 제가 아무렇게나 정한 이 방의 시작 주소 입니다. 참고로, 0x 가 뭐냐고 물어보는 사람들이 있을 텐데, 이전 강좌에서도 이야기 하였지만 16 진수라고 표시한 것 입니다. 즉, 16 진수로 152839 (10 진수로 1386553) 라는 위치에서 부터 4 바이트의 공간을 차지하며 123 이라는 값이 저장되어 있게 하라는 뜻이지요.

그렇다면 아래와 같은 문장은 어떻게 수행 될까요?

a = 10;


사실 컴파일러는 위 문장을 아래와 같이 바꿔주게 됩니다. 

메모리 0x152839 위치에서 부터 4 바이트의 공간에 있는 데이터를 10 으로 바꾸어라! 

결과적으로, 컴퓨터 내부에서는 올바르게 수행되겠지요. 
참고적으로 말하는 이야기 이지만 현재 (아마 이 블로그에 접속하는 사람들 중 99% 이상이) 많은 사람들은 32 비트 운영체제를 사용하고 있습니다. 이 32 비트에서 작동되는 컴퓨터들은 모두 주소값의 크기가 32 비트 (즉, 4 바이트.. 까먹었다면 2-3 강 참조) 로 나타내집니다. 즉 주소값이 0x00000000 ~ 0xFFFFFFFF 까지의 값을 가진다는 것이지요. 어랏! 조금 똑똑하신 분들이라면 32 비트로 사용할 수 있는 주소값의 가지수는 2 의 32 승 바이트, 즉 RAM 은 최대 4 GB 까지 밖에 사용할 수 없다는 사실을 알 수 있습니다. 맞습니다. 이 때문에 32 비트 운영체제에서는 RAM 의 최대 크기가 4 GB 로 제한되지요(즉, 돈을 마이 들여서 RAM 10GB 로 만들어도 컴퓨터는 4 GB 까지 밖에 인식하지 못합니다. 어찌 이렇게 슬플수가..) 1

여기까지는 상당히 직관적이고 단순해서 이해하기 쉬웠을 것 입니다. 그런데 C 를 만든 사람은 아주 유용하면서도 골때리는 것을 하나 새롭게 만들었습니다. 바로 '포인터(pointer)' 입니다. 영어를 잘하는 분들은 이미 아시겠지만 '포인터' 라는 단어의 뜻이 '가리키는 것(가르켜지는 대상체를 말하는 것이 아닙니다)' 이란 의미를 가지고 있습니다. 

사실, 포인터는 우리가 앞에서 보았던 int 나 char 변수들과 다른 것이 전혀 아닙니다. 포인터도 '변수' 입니다. int 형 변수가 정수 데이터, float 형 변수가 실수 데이터를 보관했던 것 처럼, 포인터도 특정한 데이터를 보관하는 '변수' 입니다. 그렇다면 포인터는 무엇을 보관하고 있을 까요? 

바로, 특정한 데이터가 저장된 주소값을 보관하는 변수 입니다. 여기서 강조할 부분은 '주소값' 이라는 것 이지요. 여기서 그냥 머리에 박아 넣어 버립시다. 이전에 다른 책들에서 배운 내용을 싹 다 잊어 버리고 그냥 망치로 때려 넣듯이 박아버려요. 포인터에는 특정한 데이터가 저장된 주소값을 보관하는 변수 라고 말이지요. 크게 외치세요. '주소값!!!!!' 

암튼, 뇌가 완전히 세뇌되었다고 생각하면 다음 단계로 넘어가도록 하겠습니다. 아직도 이상한 잡념이 머리에 남아 있다면 크게 숨을 호흡하시고 주소값이라고 10 번만 외쳐 보세요.. 

자. 되었습니다. 이제 포인터의 세계로 출발해 봅시다. 뿅

  포인터
 

다시 한 번 정리하자면

"포인터" : 메모리 상에 위치한 특정한 데이터의 (시작)주소값을 보관하는 변수!

우리가 변수를 정의할 때 int 나 char 처럼 여러가지 형(type) 들이 있었습니다. 그런데 놀랍게도 포인터에서도 형이 있습니다. 이 말은 포인터가 메모리 상의 int 형 데이타의 주소값을 저장하는 포인터와, char 형 데이터의 주소값을 저장하는 포인터가 서로 다르다는 말입니다. 응?? 여러분의 머리속에는 아래와 같은 생각이 번개 처럼 스쳐 지나갈 것입니다. 

"야 이 Psi 같은 놈아. 아까 포인터는 주소값을 저장하는 거래며. 근데 우리가 쓰는 컴퓨터에선 주소값이 무조건 32 비트, 즉 4 바이트래며!! 그러면 포인터의 크기는 다 똑같은것 아냐? 근데 왜 포인터가 형(type)을 가지는 건데!! 아아아아악" 

휴우우. 진정좀 하시고. 여러분 말이 백번 맞습니다 - 단, 현재 까지 배운 내용을 가지고 생각하자면 말이지요. 포인터를 아주 조금만 배우면 왜 포인터에 형(type) 이 필요한지 알게 될 것입니다. 

C 언어에서 포인터는 다음과 같이 정의할 수 있습니다. 

(포인터에 주소값이 저장되는 데이터의 형) *(포인터의 이름);

혹은 아래와 같이 정의할 수 도 있습니다.

(포인터에 주소값이 저장되는 데이터의 형)* (포인터의 이름);

  예를 들어 p 라는 포인터가 int 데이터를 가리키고 싶다고 하면 ...

int *p; // 라고 하거나
int* p; // 로 하면 된다

라 하면 올바르게 됩니다. 즉 위 포인터 p 는 int 형 데이터의 주소값을 저장하는 변수가 되는 것 입니다. 와우! 

  & 연산자
 

그런데 말입니다. 아직도 2% 부족합니다. 포인터를 정의하였으면 값을 집어 넣어야 하는데, 도대체 우리가 데이터의 주소값을 어떻게 아냐는 말입니까? 하지만, 여러분의 이러한 욕구를 충족시키는 연산자가 C 에 (당연히) 있습니다. 바로 & 연산자 이지요. 

그런데, 아마 복습을 철저하게 잘하신 분들은 당황할 수 도 있습니다. 왜냐하면 & 가 AND 연산자이기 때문입니다. (4 강 참조) 그런데, & 연산자는 두 개의 피연산자를 필요로 했습니다. 즉,

a & b; //o.k
a & // NOT ok

와 같이 언제나 2 개가 필요 하다는 것이지요. 그런데, 여기에서 소개할 & 연산자는 오직 피연산자가 1 개인 연산자 입니다. (이러한 연산자를 단항(unary) 연산자라 합니다) 즉, 위의 AND 연산자와 완전히 다르 다는 것이지요. 이는 데이터의 주소값을 불러 옵니다. 사용은 그냥 아래와 같은 꼴로 사용해 주면 됩니다.

& (주소값을 계산할 데이터)

백설(說)이 불여일행(行). 한 번 프로그램을 짜 봅시다. 

/* & 연산자 */
#include <stdio.h>
int main()
{
    int a;
    a = 2;

    printf("%x \n", &a); 
    return 0;
}

성공적으로 컴파일 했다면


와 같이 나옵니다. 참고로, 여러분의 컴퓨터에 따라 결과가 다르게 나올 수 도 있습니다. 사실, 저와 정말 인연 이상의 무언가가 있지 않는 이상 전혀 다르게 나올 것 입니다. 더 놀라운 것은 실행할 때 마다 결과가 달라질 것입니다. 

2 번째 실행한 것


위와 같이 나오는 이유는 나중에 설명하겠지만 주목할 것은 '값' 이 출력되었다는 것 입니다. 

    printf("%x \n", &a);

위 문장에서 &a 의 값을 16 진수 (%x) 로 출력하라고 명령하였습니다. 근데요. 눈치가 있는 사람이라면 금방 알겠지만 위에서 출력된 결과는 4 바이트(16 진수로 8 자리)가 아닙니다! (여러분의 컴퓨터는 다를 수 있습니다. 아무튼..) 하지만 저는 32 비트 운영체제를 사용하고 있습니다. 그렇다면 뭐가 문제인가요? 사실, 문제는 없습니다. 단순히 앞의 0 이 잘린 것 이지요. 주소값은 언제나 4 바이트 크기, 즉 16 진수로 8 자리 인데 앞에 0 이 잘려서 출력이 안된 것일 뿐입니다. 따라서 변수 a 의 주소는 아마도 0x001EF8D4 가 될 것입니다. 

 아무튼 위 결과를 보면, 적어도 제 컴퓨터 상에선 int 변수 a 는 메모리 상에서 0x001EF8D4 를 시작으로 4 바이트의 공간을 차지하고 있었다는 사실을 알 수 있습니다. 

 자, 이제 & 연산자를 사용하여 특정한 데이터의 메모리 상의 주소값을 알 수 있다는 사실을 알았으니 배고픈 포인터에게 값을 넣어 봅시다. 

/* 포인터의 시작 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;

    printf("포인터 p 에 들어 있는 값 : %x \n", p);
    printf("int 변수 a 가 저장된 주소 : %x \n", &a);

    return 0;
}

실행해 보면 많은 이들이 예상했던 것 처럼.... 


똑같이 나옵니다. 어찌 보면 당연한 일입니다. 

    p = &a;

에서 포인터 p 에 a 의 주소를 대입하였기 때문이죠. 참고로, 한 번 정의된 변수의 주소값은 바뀌지 않습니다. 따라서 아래 printf 에서 포인터 p 에 저장된 값과 변수 a 의 주소값이 동일하게 나오게 됩니다. 어때요. 쉽죠? 

  * 표  
 

  이제, 드디어 포인터의 핵심에 다다랐습니다. 현재 까지 우리가 배운 바로는 "포인터는 특정한 데이터의 주소값을 보관한다. 이 때 포인터는 주소값을 보관하는 데이터의 형에 * 를 붙임으로써 정의되고, & 연산자로 특정한 데이터의 메모리 상의 주소값을 알아올 수 있다" 까지 알고 있습니다. 참고로 아래에서 설명하는 * 는 앞서 포인터를 정의할 때 사용하였던 * 와 다른 의미를 지닌 다는 사실을 알고 있으세요. 

  앞서 & 연산자가 2% 부족한 부분을 채워준다고 했지만 안타깝게도 1% 가 남았습니다. 하지만 * 연산자가 이 1% 를 채워질 것 입니다.... 잠깐만. * 연산자?? 어디서 많이 들어본 것 같네요.. 맞아요. * 연산자도 & 처럼 피연산자를 2 개 가질 때 에는 곱셈 연산자로 사용됩니다. 즉

a * b; // a 와 b 를 곱한다.
a *; // Not OK

하지만 이 연산자는 위 & 처럼 1 개의 피연산자를 가지는 단항 연산자 입니다. * 연산자는 쉽게 풀이하자면

 "나(포인터)를 나에게 저장된 주소값에 위치한 데이터라고 생각해!"

  라는 의미의 연산자 입니다. 한 번 아래 예제를 봅시다. 

/* * 연산자의 이용 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;
    a = 2;

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

    return 0;
}

성공적으로 컴파일 한다면 


가 됩니다. 

    int *p;
    int a;

일단 int 데이터를 가리키는 포인터 p 와 int 변수 a 를 각각 정의하였습니다. 평범한 문장 이지요. 

    p = &a;
    a = 2;

그리고 포인터 p 에 a 의 주소를 집어 넣었습니다. 그리고 a 에 2 를 대입하였습니다. 

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

일단 위의 문장은 단순 합니다. a 의 값을 출력하란 말이지요. 당연하게도 2 가 출력됩니다. 그런데, 아래에서 *p 의 값을 출력하라고 했습니다. * 의 의미는 앞서, "나를 나에 저장된 주소값에 해당하는 데이터로 생각하시오!" 로 하게 하는 연산자라고 하였습니다. 즉, *p 를 통해 "p 에 저장된 주소(변수 a 의 주소)에 해당하는 데이타, 즉 변수 a" 를 의미할 수 있게 되었습니다. 다시 말해 *p 와 변수 a 는 정확히 동일합니다. 즉, 위 두 문장은 아래 두 문장과 10000% 일치합니다.

   printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", a);

마지막으로 * 와 관련된 예제 하나를 더 살펴 봅시다.

/* * 연산자 */
#include <stdio.h>
int main()
{
    int *p;
    int a;

    p = &a;
    *p = 3;

    printf("a 의 값 : %d \n", a);
    printf("*p 의 값 : %d \n", *p);

    return 0;
}

성공적으로 컴파일 하였다면


아마 많은 여러분들이 예상했던 결과 이길 바랍니다^^

    p = &a;
    *p = 3;

위에서도 마찬가지로 p 에 변수 a 의 주소를 집어 넣었습니다. 그리고 *p 를 통해 "나에 저장된 주소(변수 a 의 주소)에 해당하는 데이터(변수 a) 로 생각하시오" 를 의미하여 *p = 3 은 a = 3 과 동일한 의미를 지니게 되었습니다. 어때요. 간단하지요? 이로써 여러분은 포인터의 50% 이상을 이해하신 것 입니다~~! 짝짝짝짝

 자. 그럼 '포인터' 라는 말 자체의 의미를 생각해 봅시다. int 변수 a 와 포인터 p 의 메모리 상의 모습을 그리면 아래와 같습니다.

포인터 p 도, a 도 메모리 상에 각각 존재합니다만, 그 위치에는 a 의 경우 3 이 있지만 p 의 경우 a 의 주소값이 있습니다.

참고로 주소값은 제가 임의로 정한 것 입니다.


  즉, 포인터  p 는 * 를 통해 a 를 의미 할 수 (가리 킬 수) 있게 되었지요. 흔히 많은 책들은 포인터 p 가 변수 a 를 가리키고 있다 라고 말합니다. 사실 저는 이 말이 여러분에게 어렵게 다가올 까봐 여태까지 하고 있지 않았지만 아무튼, 포인터 p 에 어떤 변수 a 의 주소값이 저장되어 있다면 '포인터 p 는 변수 a 를 가리킨다' 라고 말합니다. 참고적으로 말하지만 포인터 또한 엄연한 변수 이기 때문에 특정한 메모리 공간을 차지합니다. 따라서 위 그림과 같이 포인터도 자기 자신만의 주소를 가지고 있지요. 

 이제 여러분들은 포인터에 왜 '형(type)' 이 필요한지 이야기 할 수 있는 단계가 되었습니다. (아마 여러분들 중 반 수 이상은 이미 짐작하고 계실 것 입니다.) 다시 말해 '형' 이 없다는 것은 포인터가 자신이 가리키고 있는 대상에 대해 어떠한 정보도 가지지 않아도 된다는 것 입니다. 여기서 포인터를 선언하기 위해 pointer 라는 저 만의 키워드를 이용했다고 합시다. (실제로 이런게 사용되는 것이 아닙니다;;;) 

int a;
pointer *p;
p = &a;
*p = 4;

자. 위 명령이 올바른 결과를 출력할까요? 포인터 p 에는 명백히 변수 a 의 '시작 주소' 가 들어 있습니다. '시작 주소' 란 말입니다. 즉, *p 라고 했을 때 포인터 p 에는 자신이 가리키는 대상의 시작 주소가 있지만 대상의 크기에 대한 정보가 없습니다. 헉! 컴퓨터는 *p 라고 했을 때 메모리에서 0x12345678 로 부터 (&a 가 0x12345678 이라 가정합시다) 몇 개의 바이트를 더 읽어 들어서
값을 변경해야 할 지 모른다는 말입니다. 결국 포인터는 쓸모 없게 됩니다. 

하지만, 우리가 여태까지 해왔던 것 처럼 

int a;
int *p;
p = &a;
*p = 4;

라고 한다면 어떨 까요? 컴퓨터는 0x12345678 로 부터 포인터 p 가 int * 라는 사실을 보고 "아하. 이 포인터는 int 데이터를 가리키는 구나!" 라고 알게 되어 정확히 4 바이트를 읽어 들어 값을 바꾸게 됩니다. 따라서 정확한 값이 출력될 수 있겠지요. 

여러분이 현재 까지 배운 내용에 대해 완벽하게 이해를 하고 있다면 아래와 같은 궁금증이 생길 것 입니다. 

"야. 그런데 말야. 주소값은 무조건 4 바이트 잖아? 그러면 int *p; 처럼 귀찮게 * 을 붙이지 말고 int p; 라고 한다음에 p = &a; 라고 해도 상관 없는거 아니야? 그지? 그지? 그지? " 

  훌륭한 생각입니다. 한 번 해봅시다. 

/* 될까? */
#include <stdio.h>
int main()
{
    int p;
    int a;

    p = &a;
    a = 3;

    printf("*p 의 값? : %d \n", *p);

    return 0;
}

안타깝게도 printf 부분에서 아래와 같은 오류가 출력됩니다.

error C2100: 간접 참조가 잘못되었습니다.

사실, 우리가 현재 까지 배운 내용을 바탕으로 이해해 보자면 위에서 왜 오류가 발생하는지 이해하기 힘듦니다. 그냥 단순히 * 연산자는 포인터들에게만 적용된다는 사실만을 알아 두시면 감사하겠습니다.

/* 포인터도 변수이다 */
#include <stdio.h>
int main()
{
    int a;
    int b;
    int *p;

    p = &a;
    *p = 2;
    p = &b;
    *p = 4;

    printf("a : %d \n", a);
    printf("b : %d \n", b);
    return 0;
}

성공적으로 컴파일 하였다면



    p = &a;
    *p = 2;
    p = &b;
    *p = 4;

  사실, 이런 예제까지 굳이 보여주어야 하나 하는 생각이 들었지만 그래도 혹시나 하는 마음에 했습니다. 앞에서도 말했듯이 포인터는 '변수' 입니다. 즉, 포인터에 들어간 주소값이 바뀔 수 있다는 것이지요. 위와 같이 처음에 a 를 가리켰다가, (즉 p 에 변수 a 의 주소값이 들어갔다가) 나중에 b 를 가리킬 수 (즉 p 에 변수 b 의 주소값이 들어감) 있다는 것 이지요. 뭐 특별히 중요한 예제는 아니였습니다만. 나중에 상수 포인터, 포인터 상수에 대해 이야기 하면서 다시 다루어 보도록 하겠습니다.

마지막으로, 강의를 마치며 여러분에게 포인터에 대해 완벽히 뇌리에 꽂힐 만한 동화를 들려드리겠습니다.

옛날 옛날에 .. 대략 2 년 전에 (뭐.. 전 여러분과 옛날의 정의가 다릅니다ㅋ) 변철수, 변수철, 포영희라는 세 명의 사람이 OO 아파트에 살고 있었습니다. 

int chul, sue; 
int *young;

그런데 말이죠. 포영희는 변철수를 너무나 좋아한 나머지 자기 집 대문 앞에 큰 글씨로 "우리집에 오는 것들은 모두 철수네 주세요" 라고 써 놓고 철수네 주소를 적어 놓았습니다

young = &chul;

어느날 택배 아저씨가 영희네 집에 물건을 배달하러 왔다가 영희의 메세지를 보고 철수네에 가져다 주게 됩니다.

*young = 3; // 사실 chul = 3 과 동일하다!

영희에 짝사랑이 계속 되다가 어느날 영희는 철수 보다 더 미남인 수철이를 보게 됩니다. 결국 영희는 마음이 변심하고 수철이를 좋아하기로 했죠. 영희는 자기 대문 앞에 있던 메세지를 떼 버리고 "우리집에 오는 것은 모두 수철이네 주세요." 라 쓰고 수철이네 주소를 적었습니다.

young = &sue;

며칠이 지나 택배 아저씨는 물건을 배달하러 영희네에 왔다가 메세지를 보고 이번엔 수철이네에 가져다 줍니다.

*young = 4; // 사실 sue = 4 와 동일하다

이렇게 순수한 사랑이 OO 아파트에서 모락 모락 피어났습니다..... 끝

return 0; // 종료를 나타내는 것인데, 아직 몰라도 되요. (정확히 말하면 리턴...) 

생각해 볼 문제 

* 와 & 연산자의 역할이 무엇인지 말해보세요 (난이도 : 下)

int **a; 와 같은 이중 포인터(double-pointer) 에 대해 생각해 보세요 (난이도 : 中上)


함수에서 외부의 값(실인수)을 전달받기 위해 사용되는 변수를  형식인수라한다. 이 형식인수에 외부값(실인수)이 전달되는 방식에 따라 값에 의한 호출과 참조에 의한 호출로 나뉘어 지는데, 값호출과 참조호출로 나뉜다. 일반적으로 실인수를 형식인수에 전달시 대입연산을 하는 경우와 마찬가지 동작이 일어나, 실인수의 값을 형식인수에 대입을 한다. 메모리상에선 실인수의 값을 복사한 후 형식인수에게 할당을 한다. 그 결과 같은 값을 지닌 변수가 서로 다른 메모리 공간에 2개 존재하게 된다. 이 경우 형식인수값을 조작하더라도 실인수의 값은 어떤 영향도 받지 않다. 이런 호출방식을 값호출이라고한다.


그럼 참조에 의한 호출은 어떤 것인까?

실인수의 값을 전달하는 것이 아닌 주소를 전달하는 것이다. 사실 내부적으로 보면 값호출이나 참조호출이나 같은 동작이 일어난다. 주소도 갓이므로 주소값이 형식인수에 복사가 된다. 그런데 주소값을 전달하기 위해서는 형식인수의 타입은 실인수타입의 포인터여야 한다. 즉 참조호출은 형식인수가 포인터기 때문에, 내부적으로는 실인수의 값을 알기위해서는 형식인수(포인터)에 의한 참조 방식으로 알아야 한다. 결국 이런 전달방식은 실인수에 직접 접근이 가능하다.


그럼 값호출과 참조호출의 차이점을 간단히 예를 들어보자


값에 의한 호출

위 코드의 동작후 결과 값은 3을 출력한다. 의도한바는 a의 값을 전달받아 3을 더 더해서 6이 되길 바라는 코드였다. 그런데 위에서 설명했듯이, 값에의한 호출은 값만을 형식인수에 복사하기 때문에, 실인수 a와는 아무런 상관없이 함수내부의 형식인수가 6의 값을 갖는다. 함수내부의 a라는 값은 지역변수기 때문에 함수가 리턴될 때 바로 소멸된다.


함수 내부에서 외부의 값을 다루기 위해선 포인터에의한 참조를 통해 외부값을 조작해야한다.


참조에 의한 호출

위 코드는 참조호출로 바꾼 코드다. 결과 값은 6을 출력한다.

단순히 주소값을 전달하여 내부적으로는 실인수에 접근하여 값을 조작했다. 함수내부에서 선언된 지역변수인 형식인수 a가 소멸되지만, 소멸과 관계없이 실인수 a의 값은 조작된 후다. 



그러면 이번엔 좀 더 깊게 생각해보자.


위 코드는 동적 메모리의 함수를 우해 래퍼함수인 n_call 함수를 만든 것이다. 포인터를 전달한후 동적할다을 하여 TEST라는 값을 넣었다. 그리고 실인수의 값을 출력하는 코드이다. 그런데 이상하게 제대로 동작하지 않는다.

분명 name도 포인터 변수고, name 가리키는 공간의 주소값을 넘겨주어서 그 공간에대한 메모리의 동적 할당을 하는 것인데 왜 안될까.. 이렇게 생각 될 수도 있겠다. 그러면 다음의 그림을 보자.


값에 의한 호출이 된 경우

좀 헷갈릴 수 있겠으나, 기본적인 동작은 같다. 실인수의 동적할당을 제대로 받기 위해선 실인수의 포인터를 전달해야한다. 위 그림은 형식인수가 동적할당은 받은 후 TEST를 입력받았다. 이 경우도 함수가 리턴된후 a의 값은소멸된다. 그리고 Name은 아무런 변화가 없다. 즉 값에의한 호출이다.

 단순히 Name의 값만 바꾸려 했다면 제대로 동작 했을 것이다. 이런일이 일어나는 이유는 함수내부에 있는 malloc이 포인터의 값을 변화시키는 함수기 때문이다. 때문에 포인터를 기준으로 생각해야한다.

실제로 원하는 동작을 만들기 위해선 형식인수로 &Name을 전달해야하며, 형식인수의 타입은 char **a 가되어야 한다. 그리고 함수 내부에서 형식인수는 '*'연산으로 실인수의 실제 값을 참조해야한다. 다음 그림을 참조하길바란다.


제대로 참조에 의한 호출이 된경우



2014/02/11 - [프로그래밍/C언어] - 이중포인터에 대한 이해 C언어


이경우 C언어의 문자열을 다루는 형식의 특성상 2중포인터를 사용하긴 했으나, 실제로, 다른 참조호출의 동작과 같은 것이다. 원리 또한 동일하다. 단지 위 경우는 기준점이 포인터 Name 이었기 때문이다.


+ Recent posts