ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C Language] 39. 포인터의 포인터 - C 언어
    CSE/C Language 2015. 8. 5. 11:32

    1. 포인터의 포인터란?

     이해를 돕기 위해서 배열을 먼저 예로 들어 보자.

      

      - 1차원 배열: int apple[5];

      - 2차원 배열: int banana[2][10];

      - 3차원 배열: int chocolate[3][4][5];


     1차원 배열은 관리 대상이 1차원이며 모두 5개의 요소로 구성되어 있다.

     2차원 배열은 관리대상이 2차원이다. 2차원 이라는 것은 1차원 배열의 모임이므로 이것을 '배열의 배열'이라고 칭할 수 있다. 배열의 배열이라는 것은 결국 배열을 관리하는 배열이 존재한다는 것이다.

     3차원 배열은 '배열의 배열의 배열'이 되며 2차원 배열을 관리하는 배열이라고 생각하자. 


     이를 포인터로 적용하면 아래와 같다.


      - 포인터 변수 정의: char *apple;

      - 포인터의 포인터 변수 정의: char **banana;

      - 포인터의 포인터의 포인터 변수 정의: char ***chocolate;


     포인터 변수 정의는 문자열 중에서 가장 첫 번째 문자의 주소를 가리킨다. 포인터 변수를 사용하면 하나의 문자열을 다룰 수 있다.

     포인터의 포인터 변수 정의는 배열과 같은 맥락으로 해석하면 포인터를 관리하는 포인터 변수가 된다. 즉, 포인터가 여러 개 존재하고 이를 관리하는 하나의 포인터가 존재하는 것이다. 





    2. 포인터의 포인터 변수를 왜 사용하는가?

     포인터의 포인터 변수를 사용하는 주된 목적은 다음과 같다.

     

      1. 매개변수가 포인터 배열인 경우 (문자열 집합)

      2. 명령행 인자를 사용하는 경우 (문자열 집합)


     쉽게 말하면 하나의 함수에서 사용하던 문자열들의 집합이나 2 차원 배열 등을 다른 함수에 넘겨 처리하기 위해 포인터의 포인터 개념이 필요한 것이다.

     명령행 인자도 이의 연장선상에서 이해하면 무리가 없다. 만약 포인터의 포인터라는 개념이 없다면 문자열을 하나씩 다른 함수에 넘겨서 처리해야 하는 번거로움이 발생한다. 개념상 어려운 것은 없지만 확실히 이해해 두지 않으면 사용방법상 큰 문제가 된다.

     포인터의 포인터에서 파생될 수 있는 표현식은 아래와 같은 것이 있다.


     **char_p, *char_p, &*char_p, &char)p, ...


     별표 단항 연산자가 하나 더 들어감으로써 표현식이 조금 복잡해 보인다. 이것 때문에 많은 사람들이 포인터의 포인터를 어려워한다.

     별표 하나만 붙여야 하는지 2개를 붙여야 하는지, 넘길 때 어떻게 표현해야 하는지 등 생각보다 초보자에게 넘어야 할 산이 많다.






    3. 포인터의 포인터 변수 정의 방법과 초기화

     포인터의 포인터 변수 정의 방법


    1
    2
    3
    4
    5
    6
    int **imsipp_int;
     
    char **imsipp_char;
     
    float **imsipp_float;
     
    cs


     정의 방법은 포인터 변수 앞에 '*' 만 하나 더 추가하면 된다. 이것으로써 포인터를 관리하는 포인터 변수가 생성된 것이다.

     포인터의 포인터 변수는 그 자체만으로는 아무 의미가 없다. 방금 언급한 것처럼 포인터의 포인터 변수는 포인터를 관리하는 포인터이다. 즉, 관리 대상이 존재할 때만 포인터의 포인터가 의미가 있는 것이다. 



     포인터의 포인터 변수 초기화


    1
    2
    3
    4
    5
    6
    7
    8
    char zero;
    char *one;
    char **two;
     
    zero = 'u';
    one = &zero;
    two = &one;
     
    cs









     zero는 1 바이트를 할당받고 이곳에 u를 저장한다. one은 포인터 변수로 zero를 가르키고 있으며 zero의 주소를 저장한다.

     two는 포인터의 포인터 변수로 one을 가리키고 있고 one이 저장되어 있는 주소를 저장한다. 주의할 점은 one 안에 저장된 주소 값(zero의 주소 값)이 아니라 one이 저장된 위치를 저장한다는 것이다. 위 그림으로 파악할 수 있는 사실은 다음과 같다.


     1. 포인터의 포인터 변수는 일종의 포인터 변수이다. 그러므로 주소 값만 저장할 수 있다.

     2. 포인터의 포인터 변수가 관리하는 대상은 포인터 변수이다. 관리하는 대상이 포인터 변수이기 때문에 주소 값이라고 무조건 넣을 수는 없다는 것이다. 가령 two에 zero의 주소 값을 넣으려고 하면 문법 에러가 발생한다. two에 zero의 주소 값을 항당하면 two가 관리하는 대상이 u라는 아스키 값이 되므로 문법적으로 맞지 않게 된다. 포인터 변수는 어느 주소 값이든 모두 받아들이지만 포인터의 포인터 변수는 아무 주소 값이나 다 넣을 수 없다.




    1
    2
    3
    4
    5
    6
    7
    8
    9
    char *one[3];
    char **two;
     
    one[0= "palpit";
    one[1= "Everytown";
    one[2= "Town";
     
    two = one;
     
    cs









     이전의 예제는 two = &one 이라는 형식을 사용했고, 바로 위 예제는 two = one 이라고 '&' 없이 바로 할당했다.

     one은 포인터 배열이기 때문에 &가 없어도 된다. 이를 먼저 이해하고 다음 예제를 분석해 보기로 하자. 위의 간이 예제를 수행하면 포인터의 포인터 변수는 다음과 같은 메모리 구조를 가진다.


    1
    2
    char *one[3];
    char **two; 
    cs







     4개의 변수는 초기화 되지 않았기 때문에 모두 물음표로 표기했다. 포인터의 포인터 변수도 일종의 포인터 변수이기 때문에 4 바이트를 할당받는다는 것에 주목하자.



     

    1
    2
    3
    4
    one[0= "palpit";
    one[1= "Everytown";
    one[2= "Town";
     
    cs







     문자열 3개가 메모리의 어딘가(0x2000, 0x2007, 0x2010)에 저장되고 이 주소를 one[0], one[1], one[2]가 나누어서 저장하고 있다.



      

    1
    2
    two = one;
     
    cs





     마지막으로 two는 문자열을 직접 가리키는 것이 아니라 문자열들을 가리키고 있는 포인터 배열을 가리킨다. two 는 one[0], one[1], one[2] 중에서 가장 첫 번째 요소의 가장 첫 번째 주소 값을 가리키므로 0x1000을 저장한다. 0x2000을 저장하지 않는다는 것을 눈여겨 보도록 하자.


     이전의 예제에서 one은 zero가 저장된 위치 하나만을 가리키고 있었다. 하지만 위 예제에서는 one은 문자열 3개를 관리하고 있는 포인터 배열이다. 그러므로 one을 two에 할당할 때 &가 필요 없는데 만약 문자열 하나만을 포인터의 포인터 변수가 가리키도록 하고 싶다면 다음처럼 사용해야 한다.


    1
    2
    3
    4
    two = &one[0];    
    two = &one[1];
    two = &one[2];
     
    cs



     출력은 아래와 같이 할 수 있다.


    1
    2
    3
    4
    two = &one[0];
    putchar(**two);        // print 'p'
    puts(*two);            // print 'palpit'
     
    cs



     위와 같이 사용하면 하나의 문자열만을 다루게 되는데, 포인터의 포인터 변수에 대한 특성을 고려하지 않은 것이므로 추천하지 않는다.







    4. 포인터의 포인터 참조

     가장 간단한 포인터의 포인터 참조 예제를 살펴보자. 포인터 배열을 포인터의 포인터 변수가 할당받아 처리한 예제이다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
     
    /*
     * pointe.c
     *
     *  Created on: 2015. 8. 5.
     *      Author: Yeonsu
     */
     
    #include <stdio.h>
     
    int main(void) {
     
        int i;
        char *tmp[3];
        char **imsipp;
     
        tmp[0= "palpit";
        tmp[1= "every";
        tmp[2= "town";
     
        imsipp = tmp;
     
        for (i = 0; i < 3; i++)
            puts(tmp[i]);
     
        for (i = 0; i < 3; i++)
            puts(*imsipp++);
     
        return 0;
    }
     
    cs










     함수의 인자로 사용한 포인터의 포인터 예제를 보도록하자.




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
     
    /*
     * pointe.c
     *
     *  Created on: 2015. 8. 5.
     *      Author: Yeonsu
     */
     
    #include <stdio.h>
     
    void join(char **);
     
    int main(void) {
     
        int i;
        char *tmp[3];
     
        tmp[0= "palpit";
        tmp[1= "every";
        tmp[2= "town";
     
        join(tmp);
     
        return 0;
    }
     
    void join(char **imsipp) {
        int i;
     
        for (i = 0; i < 3; i++)
            printf("%s ", imsipp[i]);
     
        puts("");
        for (i = 0; i < 3; i++)
            printf("%s "*imsipp++);
    }
     
    cs







    댓글

Designed by Tistory.