Programming - cpueblo.com

포인터 이야기


글쓴이 : 유광희 날짜 : 2002-05-13 (월) 21:21 조회 : 7056
번  호 : 2437
게시자 : 채화영   (dragnfly)
등록일 : 1997-04-18 20:43
제  목 : [강좌]포인터 이야기 (1)                 


 아래글은 저희 컴 서클(아이콘)에서 포인터를 공부하면서

 그동안 공부한 내용을 정리한 것입니다.

 내용에 특정인의 고유명사(?)가 나오긴 하지만 포인터에

 대해 정리하는데 유용하리라 생각합니다.

 소프트 동에서 얻은 글을 인용하기도 했기에 이렇게 글을 

 올립니다. 
아이콘 씨 강좌 


                        ***** 배열과 포인터 *****   
  1. 배열의 개념
    1. 배열의 소개
    2. 간단한 예제 들기 
  

  2. 1차원 배열
    마찬가지로..

  3. 2차원 배열
  4. 3차원 및 다차원 배열
  








 1. 포인터의 개념.
     1. 포인터의 소개(가리키는것.. 약간의 예제)
           1. 개념..
           2. 간단한 예제하나와 그림을 그려서 설명

 
 2. 1차원 포인터 
    1. 1차원 포인터 소개..
    2. 간단한 예제와 그림을 그려서 설명.. 

 3. 2차원 포인터 
    1. 2차원 포인터 소개 
    2. 예제. 그림

 4. 3차원 및 다차원 포인터 
    1. 3차원 포인터 간단 소개 , 예제 그림
    

 5. 포인터 배열...



 5. void/null 포인터
    1. void, null 포인터 ..소개 
    2. 예제



 6. 배열과 포인터와의 미팅?

   1. 씨에서 배열과 포인터와 의 관계
   2. 예제...... 그림..
   3. 넘나드는 것 확인 ..
   4. *i++, (*i)++, *++i, ++*i관계 확인
   
  1. 글의 방향

  이 강좌는 내가 씨 특히 포인터를 공부하면 여타 다른 문법책에서
다루지 않은 것 그리고 이해하기 힘든것은 (나도 아직 모호하지만)
최대한 내 목소리로 쓰려고 노력했다. 예제도 왠만하면 별로 좋지
는 않아도 내가 만들려고 했고 내용이 좀 틀려도 내가 이해한대로 썼다.
그리고 내가 의문이 난 부분을 풀어보려고 했고.. 그렇게 했다.
 그렇게 쓰지 않으면 그냥 시중에   있는 다른 씨책과 다를게 없어서
....



                                         < 아이콘 2기 채화영 >

 Ⅰ. 포인터의 개념.

   1. 포인터는 몰까
        음음.. 포인터(pointer)는 말 그대로 어디를 가리킨다는 뜻이다. 
 내가 알기로는 공부하면 할 수록 포인터의 기능을 빼면 씨는 시체라는 
생각이 든다는 것이다. 포인터는 단순히 어느 변수를 가리키는 것 이외
파일의 첫머리라던지 거의 프로그램에 필요한 모든 요소를 가리킬 수있다.
여기서 가리킬 수 있다는 말은 우리가 가리켜진 변수를 포인터를 이용해
서 간접적으로 다룰 수 있다는 말이다. 일종의 끄나풀 같은 존재이다.

어느날 화영이는 집으로 가다가 예쁜 여학생을 만났다. 한 눈에 반한 화영
이는 그 여자 아이의 집을 수소문 해보았다. 그랬더니 명국이라는 놈이 그 
여자 아이의 주소를 알고 있다는 것이다(이 비유는 2학기 프실때 교재에서 
인용). 화영이는 그 여자 아이의 주소를 알기 위해 명국이를 찾아가야 한다
. 화영이가 명국이를 찾아 갔더니 명국이는 손가락으로 그 여자 아이집을 
가리킬 뿐이었다. 그리고는 주소를 대라고 했더니 100번지라고 했다.

 이것을 씨로 비유해보자. 화영이는 프로그램을 짜는 바로 아이콘 후배이고
명국이는 포인터 변수, 여자아이는 임의의 어떤 변수이다. 명국이는 여자의 
주소를 알고 있다.


 200 명국   ---->   100 여자 
   100                'f'

 위의 표기법에 익숙해지자(이것은 캠씨에서 따온것..). 변수가 메모리에 
위치하는 꼬라지를 간략하게 표시하려고 만든 형식이다.

 [변수의 주소] [변수이름] 
        [변수값]

 그러니까.. 위의 그림은 200번지의 명국이는  100번지의 여자변수의 값을
가지고 있는 셈이 된다. 이것을 씨로 나타내면

에제1)

 #include 

 void main(void)
 {
    int *myungkuk, girl='f';
    
    myungkuk = &girl;
    printf("%d\\n\\n", girl);  
    printf("%p\\n", &girl);   

    printf("%p\\n", &myungkuk); 
    printf("%p\\n", myungkuk);  
    printf("%d\\n", *myungkuk);  /* myungkuk 이 가리키고 있는 변수의
                                   실제 값 */

}

결과>

   102(f의 아스키값)
   100
   200
   100
   102

 (위의 값은 컴퓨터가 임의로 정하기 때문에 다를 수 있고, 더 복잡하다.)

 위의 결과가 이해가 될 것이다. 안되면 아이콘 선배한테 따뜻한 밥과
 오늘 굉장이 예쁘다거나 멋있다고 하면 이해를 시켜줄 것 같다^^.

  위에도 말했듯이 포인터는(정확히 말하면 포인터 변수) 임의 변수의
  주소를 가리키는 변수이고 이 포인터를 사용해서 간접적으로 변수의
  값을 취하거나 변경시킬 수가 있다.

  아래 예제를 실행 시켜 보자.

 예제 2)

  void main()
  {
        int age[8] = { 19, 20, 21, 22, 23, 24, 25, 26 };
        int i, *p= &age[0];

for(i=0; i<8; i++) {
            printf("%d\\n", *p);
            p++;
        }
  }


  *** 팁   *과 &의 뜻을 알기

   *의 뜻은 세가지이다(나열식은 내가 제일 싫어한다... 하지만
   어쩔수가 없다.. 으씨)

   1. 곱하기로 .
   2. 포인터 변수 선언할때
   3. 포인터 변수가 가리키는 변수의 실제 값을 구할때

        1. 곱하기는  a=2*3하면 a에 6이라는 값이 들어가게 된다.
        2. 포인터 변수 선언할때 위의 예제에서도 많이 나오지만
     아래와 같이 선언 했다면

             char *i;
       char: i은 포인터 변수이다.
       *: i는 1차원 포인터이다.
       i;포인터 변수명은 i이다.

     요런 뜻이 된다.

   4. 포인터가 가리키는 실제값은 예제 1)에서

   printf("%d\\n", *myungkuk);  

    이 부분이다. 포인터 변수는 girl이라는 변수를 가리키고 있고 
 *myungkuk하면 가리키는 변수의 실제값을 찍으라는 말이다. 그래서 
 결과는 102가 된다. 


     &의 뜻.....

   &는 2항 연산자로는 bit and 연산을 한다(연산자 복습)
   또 다른 뜻으로는 변수의 주소값을 넘겨주게 된다.
   마찬가지로 예제 2)에서 

    printf("%p\\n", &girl);  

  girl의 값을 찍는게 아니라 주소값을 찍는다. &girl하면 girl의 
주소값을 넘긴다.(100이라 가정)

 
  II. 1차원 포인터.

     1. 소개
        앞에서 벌써 말한게 1차원 포인터이다. 1차원이라는 말은
 여자라는 변수를 한번만 가리킨다는 것이다. 2차원 포인터는 여자변수
 를 가리키는 포인터를 또 가리키는 포인터를 말한다.
   이 이야기를 계속 꼬면 한 친구 돌게 만드는 것 일도 아니다.

  1차원 변수 선언하고 값을 얻는 것은 앞에서 했고 포인터의 증가에 대해
서 알아보자. 
 
예제3)

#include <stdio.h>

void main(void)
{
    int age[5] = { 20, 21, 22, 23, 24 };
    int *p;

    p = age;                  
    p = &age[0];               

    printf("%p: %d\\n", p, *p);
    p++;
    printf("%p: %d\\n", p, *p);
    p+=2;
    printf("%p: %d\\n", p, *p);
    p++;
    printf("%p: %d\\n", p, *p);
}

결과) 
   100  : 20
   102  : 21
   106  : 23
   108  : 24

  (앞에도 말했지만 실제로는 주소는 16진수로 나타내어 진다. FFEE, FFE2
식으로 나타나지만 설명이 용이하게 그냥 임의 주소를 씀.)

  이 예제에서 우리는 두 가지 이야기를 해야한다. 포인터는 그 형태가 어떻
든지 2바이트라는 것이고 또 다른 하나는 포인터의 증가이다. 
 

 앞에서 들었던 비유를 다시 생각해보자. 명국이는 여자 아이의 집을 손가
락으로 가리켰다고 했다. 마찬가지로 포인터가 변수를 가리키는데에는 단순
히 집게손가락정도의 정보만 있으면 된다. 이때 필요한 양은 2바이트이고 
변수의 int와 양이 같다. (원거리 메모리를 쓸 경우 4바이트이지만 여기서
는 2바이트로 한정한다). 즉 2바이트 정도의 메모리만 있으면 왠만한 메모
리의 위치를 가리킬 수 있다는 말이다. 그러니까 포인터가 가리키는 변수
나 배열이 어떻것이든(char, int, long이든) 2바이트이면 다 가리킬 수
있다는 말.
 

 ****** 팁 : 비트, 바이트, 워드.... 
 
 컴퓨터에서 크기를 나타내는 단어로 비트, 바이트, 워드가 있다. 단순히 
비트는 최소단위이고 바이트는 2비트, 워드는 16비트 이렇게 알고 있으면 
모르는만 못하다. 머리속에서 이해하자.

 한개의 전구를 생각해보자. 깜빡깜빡하면 켜져 있는 경우, 그리고 꺼진 경
우 두가지를 가리킬 수있다. 이를 비트라고 하고, 2개의 전구는 4가지의 경
우를 가리킬 수있다. 8개의 전구는 2의 8승인(계산계산) 256가지의 경우를 
가리킬 수 있다. 즉 8개의 전구만 있으면 어느 것은 켜고 꺼고 해서 256가
지의 정보를 나타낼 수있다. 이는 엄청난 양이다. 왠만한 문자코드는 256가
지로 다 가리킬 수 있다. 
 하지만 워드가 남아 있다. 워드는 16비트로 2의 16승로 약 64K를 가리킬 
수 있다.

 다시 원 위치로 돌아와서 .. 예제 3)을 보자 p=age 라는 표현이 보인다. 
배열에서 배웠겠지만 age는 배열 age의 첫 주소값을 갖고 있다. 배열의 첫 
요소는 age[0]이니까 배열의 첫주소는 age[0]의 주소이다. &age[0]라고 써
도 상관은 없지만 같은 뜻으로 age를 쓸 수 있다. 외우기 위한 말로 배열의
 첫 주소는 배열에서 뒤의 한[]를 없앤것이 주소라는 말이 있다(배열 강좌 
참고). 모르면 선배에게 물으세용..

그림 5>
 200 p  ---->  100 age[0]  102 age[1]   104 age[2]  106 age[3]  108 age[4] 
  100             20         21           22           23          24

 메모리 구조가 위와 같다. 예제3을 실행시키면 말이다.포인터 변수 p가 
배열 age의 첫 주소를 갖고 있고 가지고 있다는 말을 가리킨다는 말로 
대신 할 수 있다. 이 포인터는 int형이므로 2바이트씩 증가하게 된다.
포인터는 자기가 가리키는 변수의 형태에 따라 char형이면 1바이트씩 long
형이면 4바이트씩 증가하게 된다(글로 대신하기 어려운 부분이다. 선배한
테 설명을 부탁한다)
   printf("%p: %d\\n", p, *p); 
 
 그림 5> 의 상태에서 위의 printf명령(실제론 함수다)을 쓰면 포인터
p의 값인 100을 찍고 p가 가리키는 변수의 실제값(100번지에 위치한 변수
age[0]의 값)을 찍는다. 이 표현이 *p이다. *의 뜻은 포인터 변수가 가리
키는 변수의 실제값을 찍는다면 표현이다. 
 그리고는 p++해서 포인터를 증가시켰다. 이를 다시 그리면 

 200 p        100 age[0]  102 age[1]   104 age[2]  106 age[3]  108 age[4] 
  102             20         21           22           23          24
   |                         |
   +--------------------------

 이렇게 되었다는 말이다. 두바이트가 증가해서 이제는 102번지를 가리키게 
되었고 실제로는 age[1]를 가리키게 되었다. 다시 printf해서 찍으면 102와
21이 찍히게 된다. 이번에는 p+=2;를 해서 두번 증가하게 된다. 그러니까...
2바이트씩 두번이니 4바이트 증가하게 된다. 
 

 200 p        100 age[0]  102 age[1]   104 age[2]  106 age[3]  108 age[4] 
  106             20         21           22           23          24
   |                                                    |
   +----------------------------------------------------+

 이제 포인터 변수 p는 106번지를 가리키고(p는 106이라는 값을 가지고 있다),
printf를 쓰면 106과 22가 찍히게  된다.

예제 4)

#include 

void main(void)
{
    char a[3], *p;

    p = a;
    p++;
    p++;
}

 위의 예제는 어떨까.. 포인터 변수는 char형이고 배열 a의 첫주소를 
가리키고 있는데 한번 증가시키면? 그리고 다시 한번 증가시키면?

 200 p ---> 100 a[0]  101  a[1]   102 a[2]   (배열의 주소가 1바이트씩  
  100          ?          ?          ?        증가하고 있음)

 증가시키면 .. char형이니 1바이트 증가한다.

 200 p      100 a[0]  101  a[1]   102 a[2] 
  100          ?          ?          ?     
|                      |
   +----------------------+   

 다시 증가시키면..

 200 p      100 a[0]  101  a[1]   102 a[2] 
  100          ?          ?          ?     
   |                                 |
   +---------------------------------+

 예제 4에서 long a[3], *p;로 바꿔서 위의 그림같이 메모리의 형태를 
그려 볼것..


번  호 : 2438
게시자 : 채화영   (dragnfly)
등록일 : 1997-04-18 20:44
제  목 : [강좌]포인터 이야기 (2)                 

 4. 포인터와 문자열의 관계
 
 포인터를 쓰는 이유 중에 가장 많은게 문자열을 다루기 위해서이다.
아직 포인터 배열을 안 배워서 여기서는 간략하게 다루고 포인터 배열을 
할 때 다시 할 것이다. 


예제6)
1#include 
2
3void main(void)
4
5   char name[10] = "gildong";
6    int i;
7
8    printf("%s\\n", name);
9    for(i=0; i<7; i++) {
10        printf("%c", name[i]);
11    }
12    printf("\\n");
13}

 여기서는 char형 배열 name을 선언하고(10칸을 선언했다) gildong을 배열에
대입했다. name의 배열안에 gildong이 어떻게 들어가 있는지는 배열을 
배웠다면 잘 알것이다. gildong을 출력하기 위해서는 8행 처럼 printf에서 
%s를 쓰고 인자로 배열의 첫주소를 넘겨주면 출력이 된다.
 아니면 for문을 사용해서 배열의 요소를 하나씩 다 출력하면 된다. name[0],
name[1], name[2].. 씩으로 .. 그런데 gildong은 7자이지만 배열은 10칸이니
낭비가 있다.. 만약에 이런 배열이 여러개를 선언하면 메모리 낭비가 심하고 
10자를 넘기는 문장이 있다면 또 더 크게 잡아야 하고 이럴때 포인터를 사용
하면 간단하게 해결할 수 있다.

예제7)
1#include <stdio.h>
2
3void main(void)
4{
5    char *name = "gildong";
6    int i;
7
8    printf("%s\\n", name);
9    for(i=0; i<7; i++) {
10        printf("%c", *(name+i));
11    }
12    printf("\\n");
13
14}
  
 예제7)은 똑같은 것을 포인터를 사용해서 나타냈다. 8번은 예제6과 같고
5행에 char *name="gildon"; 이라고 적어서 name이라는 포인터를 선언함과
동시에 gildong이라는 문자열을 가리키도록 초기화까지 했다.
 메모리의 구조는 아래와 같다.

  200 name ---> 100  101  102  103  104  105  106  107  108  109  110 
   100           g    i    l    d    o    n    g    \\0

5행과 같이 적으면 name이라는 포인터가 char형으로 선언되고(그래도 포인터는
2바이트가 소요된다.) 메모리 어디선가(여기서는 100번지)에 gildong이라는 
문자열이 들어가게 된다. 그리고 name은 gildong이라는 문자열을 가지고 있는
배열의 첫 주소를 가리키게 된다. 당연히 배열의 이름은 모른다 단지 주소를
넘겨 받게 된다. 이것을 가지고 gildong을 다루게 된다.

아래를 실행시켜 보자.

예제8)
#include 

void main(void)
{
    char *name = "gildong";
    int i;

    printf("%s\\n", name);
    for(i=0; i<7; i++) {
        printf("%c", *(name+i));
    }
    printf("\\n");
    puts(name);
    while(*name) {
        putchar(*name);
        name++;
    }

}


  III. 2차원 포인터

 아까도 말했듯이 2차원 포인터는 포인터의 포인터이다. 그러니까,
어떤 변수나 배열을 가리키는 포인터를 가리키는 포인터 변수를 말
한다. 2차원 포인터는 1차원 포인터의 주소값을 가짐으로써 포인터
가 되는 것이다.

아래의 예제를 보자

 예제9)

#include <stdio.h>

void main(void)
{
    int a=100, *p, **pp;

    p = &a;
    pp = &p;

}


 2차원 포인터의 선언에서 
  
   int **p;

   p : 변수명
   * : p의 속성은 포인터이다.
   int * : 포인터 p는 1차원 포인터를 가리키는 2차원 포인터이다.

  *** 팁

 여기서 알아야 할 것은 캐스트 연산자에서 (char *), (int *)... 이런식
으로 쓰는 이유이다. (chat *)자체가 바로 char형의 1차원 포인터를 의미
한다. 포인터 캐스트 연산자는 난중에 void포인터에서 주로 쓰인다.
그럼,,, 이만..


 예제10)

1#include 
2
3void main(void)
4{
5    int a=100, *p, **pp;
6
7    p = &a;
8    pp = &p;
9
10    printf("a ->  %d, %p\\n", a, &a);
11    printf("*p -> %p, %p\\n", p, &p);
12    printf("**p-> %p, %p\\n", pp, &pp);


}

결과 )

    a -> 100, FFF4
    *p -> FFF4, FFF2
    **p -> FFF2, FFF0

위의 예제는 2차원 포인터를 설명하기 위한 예제이다.


 FFF0 **pp --->  FFF2  *p---> FFF4  a
   FFF2           FFF4         100

 5행과 같이 선언하면 이런 꼴이 된다. 2차원 포인터 pp는 1차원
포인터 p를 가리키고 1차원 포인터는 변수 a를 가리키게 된다. 
위의 기호의 의미를 잘 알아야 한다. 
다시 말하면 ..

 [변수의 주소] [변수이름] 
        [변수값]


그래서 결과를 출력하면 위의 결과 같이 된다.


예제11)

#include 

void main(void)
{
    int a=100, *p, **pp;

    p = &a;
    pp = &p;

    printf("%d\\n", **pp); 
    printf("%p\\n", *pp);  
    printf("%p\\n", pp);   
    printf("%p\\n", &pp);  


}

결과)
100
FFF4
FFF2
FFF0


 선언은 예제 10)과 같고, 2차원 포인터는 위와 같이 4가지의 경우로 
쓰인다. 


    4 ---> FFF0 **pp --->  FFF2  *p---> FFF4  a
              FFF2           FFF4         100
               |              |            |
               3              2            1  


 이해가 되었으면 좋겠다.

 
 IV. 3차원 포인터 ..

 3차원 포인터는 2차원 포인터의 포인터이다. 재미있게 포인터의 포인터의 
포인터이다. 3차원 포인터는 2차원 포인터의 주소를 가짐으로써 2차원 포인터
를 가리킨다.
 설명은 2차원 포인터와 동일하다. 아래의 예제를 보면서 위의 2차원 포인터
대신에 3차원으로 생각해서 분석해보길 바란다.

예제12)
#include 

void main(void)
{
    int a=100, *p, **pp, ***ppp;

    p = &a;
    pp = &p;
    ppp = &pp;

}

한가지만 덧붙이면 
 
  int ***p에서
 
  p : 변수명
  * : p는 포인터 형이다.
  int ** : 포인터 p는 2차원을 가리키는 포인터이다. (즉 3차원 포인터이다)


예제13)
#include 

void main(void)
{
    int a=100, *p, **pp, ***ppp;

    p = &a;
    pp = &p;
    ppp = &pp;

    printf("a ->  %d, %p\\n", a, &a);
    printf("*p -> %p, %p\\n", p, &p);
    printf("**p-> %p, %p\\n", pp, &pp);
    printf("***p -> %p, %p\\n", ppp, &ppp);


}

예제14)

#include 

void main(void)
{
    int a=100, *p, **pp, ***ppp;

    p = &a;
    pp = &p;
    ppp = &pp;

    printf("%d\\n", ***ppp);
    printf("%p\\n", **ppp);
    printf("%p\\n", *ppp);
    printf("%p\\n", ppp);
    printf("%p\\n", &ppp);


}


 3,4... 이런식으로 계속 확장할 수도 있지만 실제로는 2차원 이상은
잘 안 쓰이는 것 같다.


 V. 포인터 배열 

 앞에서도 밝힌 바와 같이 여기 강좌(강좌라 카기 모하지만) 책에 있는
것은 되도록 피하고 싶다. 내가 손아프게 안쳐도 다른책에 다 있어서
말이다. 
  포인터 배열은 한마디로 일정하지 않은 문자열을 메모리 낭비적게 
처리하기 위해서 사용한다. 말그대로 변수를 쭉 나열한게 배열이라면
포인터를 쭉 나열해서 일련 번호를 붙인게 포인터 배열이다.


예제15)

#include 

void main(void)
{
    char name[][10] = { "inyoung",
                        "sunyoung",
                        "mikyung",
                        "boyoung",
                        "songyi"};
    int i;

    for (i=0; i<5; i++) {
        printf("%s\\n", name[i]);
    }
}

위의 예는 그냥 배열만 사용한 예이다. 배열에서 뒤에 한개의 []를 없애면
그 배열의 주소값이라는 것을 말한 적이 있다. 그래서 for문으로 name안의
이름들을 차례로 출력해준다. 하지만 이 경우 메모리 낭비가 심한게 10개씩
5개를 잡았으니 50바이트를 설정해놓고 실제로는 그보다 적은 양을 사용한다
... 그래서 낭비라는 것인데 항상 제일 긴 문장에 맞게 배열을 잡느라 귀찮
기도 하지만 낭비가 생기는 것도 무시 못한다. 
 그럼 포인터 배열을 쓰면 .. 

예제16)

1#include <stdio.h>
2
3void main(void)
4{
5    char *name[] = { "inyoung",
6                    "sunyoung",
7                    "mikyung",
8                    "boyoung",
9                    "songyi" };
10    int i;
11
12    for (i=0; i<5; i++) {
13        printf("%s\\n", *(name+i));
14    }
15
16    printf("%c%c%c\\n", *(*(name+4)+0), *(*(name+4)+1), *(*(name+4)+2) );
17
}

 위의 예제는 이름들을 차례로 출력하고 마지막에 songyi의 son을 출력하는
프로그램이다. 5행과 같이 선언하면 

  
  name[0] ---->    inyoung
  name[1] ---->    sunyoung
      .
      .
  name[4] ---->    songyi

 이렇게 name포인터들이 각각 문장들을 가리키게 된다. 
 다시 좀 더 자세하게 그리면

 name[0]   name[1]   name[2]    name[3]    name[4]
   |          |        |           |         |
   |          |        |           |         +- s o n g y i \\0
   |          |        |           +--- b o y o u n g \\0
   |          |        +-----m i k y u n g \\0
   |          +---- s u n y o u n g \\0           
   +--- i n y o u n g \\0


 name이 포인터 배열에 첫 요소의 주소값이 되니까, name에다 +1하하면 
name[1]의 주소로 이동해서 *(name+1)하고 실제로 name[1]이 가리키는 
실제 요소값을 출력하면 sungyoung라고 출력이되는것이다.
 만약에 son을 출력하려면 (name+4)를 찾아가서 songyi에서 son를 가져
와야 한다. 그러려면 songyi의 첫 주소값 *(name+4)을 찾아서 거기서 
첫번째 두번째 세번째를 가져와야 하니까.. 첫번째 s의 주소값은
(*(name+4)+0)이고 여기의 실제 값이 's'이니 *(*(name+4)+0)이다.
16행의 나머지도 살펴보길 ....

 *** 팁 만약에 그냥 배열만으로 출력하려면?

예제17)


#include 

void main(void)
{
    char name[][10] = { "inyoung",
                        "sunyoung",
                        "mikyung",
                        "boyoung",
                        "songyi" };
    int i, j;

    for (i=0; i<5; i++) {
        for(j=0; j<10; j++) {
            if ( name[i][j] == NULL ) continue;
            printf("%c", name[i][j]);
        }
    printf("\\n");

    }
}

선배한테 설명을 부탁하는게 좋을듯 (용돈이 풍족해야?)


.

번  호 : 2439
게시자 : 채화영   (dragnfly)
등록일 : 1997-04-18 20:44
제  목 : [강좌]포인터 이야기 (3)                 

 VI. void/ null 포인터 

 씨를 좀 짜봤다면 눈에 박히도록 본 것이 void인데 ..
void main(void) 이 문장이 된다. 이때 앞에 void는 넘겨주는 값이 없다
()안의 void는 넘겨받는 값도 없다는 라는 뜻이 된다.
 그런데 포인터에서 void라는 말은 어떤 형태의 변수나 배열도 가리킬수 
있다는 말이다. 이태까지는 int 포인터는 int형의 변수를 가리킬 수 있었
다.  
 int a;
 char *p;
라고 선언했다면 p=&a;는 틀린 말이된다. 왜냐면 p는 char형의 변수만
가리킬수 있으니까 하지만 ..
 int a;
 void *p;
 라고 했다면 p=&a;는 틀리지 않는다. 이때 p는 void형으로  모든 형식의
변수를 가리킬수 있다는 말이된다. 그게 모 어땠는데 하면 할말이 없지만
쓰일 곳이 있으니까 쓰지.. 선언은 void로 해 놓고선 캐스트 연산자로
사용해서 필요한만큼 자료를 뽑아 쓸수 있다. int a변수에서 p를 (char *)을
사용하면서 실제값을 뽑으면 한바이트씩 분할해서 뽑힌다. 잔인하게 쪼개서
....

 예제18) 

1#include <stdio.h> 
2
3void main(void)
4{
5   char seho[] = "rabbit!!";
6    void *p;
7    int i;
8
9    p = seho;
10
11    for(i=0; i<8; i++) {
12        printf("%c", *((char *)p+i)  );
13    }
14
15    printf("\\n%c%c\\n", *((char *)p+2), *((char *)p+3) );
16}

예제 18)은 void포인터 사용예이다. p를 void포인터로 선언했고, 난중에 
seho[]배열안에 있는 rabbit!!을 찍기 위해서 for문을돌렸다. 관심을 가져
야 할부분은 *((char *)p +i)인데 p는 void형이니 난중에 값을 참조하려
면 (char *)과 같은 캐스트 연산자가 필요하다. 왜냐면 int든지 char든
지 미리 포인터의 현을 정해주면 주소값에서 2바이트 1바이트씩 취한다
는 것은 알고 있지만 void는 그런 형태가 없으니 쇠대가리 컴퓨터는 
몇바이트 취해야 하는지 모른가 그래서 일일이 캐스트를 써서 알려주
어야 하는것이다. 문자열이니 char *을 사용했다.
 15행은 bb를 뽑아낸 명령이다.


 이제 null 포인터. null 포인터는 이상하게 생각말자 책에는 별의 
별 표현으로 혼동시켜놨다. 간단히 0의 값을 가지고있는 주소값이니까
0x00을 가지고 있는 포인터이다. 이것은 문자열 끝에 \\0도 일종의 
null 포인터이고 함수에서 에러가 난 경우 돌리는 포인터 값이 
주로 null 포인터이다. 이때가지 심심잖게 null 포인터를 볼수 있었다.
우선 예제17)에서는 문자열 끝에는 항상 null 포인터가 있다는 것
을 생각해서 if문을 사용해서 만족하면 continue를 사용했다. 그리고
위의 seho[]배열의 seho[8]은 0x00으로 null 포인터이다.
 안녕하세요 터보씨 263쪽의 *strcmp()함수를 보면 s1, s2가 같으면
0을 돌린다고 했는데 그개 null인 것 같다(이건 확실치 않다)



 VII. 배열과 포인터의 미팅

 ... 여자 친구나 좀 소개시켜주지...쩝.
 배열과 포인터는 같은 종족이다. 배열을 쓰는 동시에 포인터도 쓰이고
포인터를 쓰면서 배열표현으로 바꿔 쓸 수도 있다. 이말은 예시당초
씨 컴파일러가포인터와 배열을 구별하지 않고 하나로 취급한다는 말이
다. (이설명은 캠퍼스씨에서 인용)

 예제19)

1#include <stdio.h>
2
3void main(void)
4{
5    int a[] = { 100, 200, 300 };
6
7    printf("%d %d %d\\n", a[0], a[1], a[2]);
8    printf("%d %d %d\\n", *(a+0), *(a+1), *(a+2) );
9    printf("%d %d %d\\n", (a+0) - &a[0],
10                         (a+1) - &a[1],
11                         (a+2) - &a[2] );
12
13}

결과) 
   100 200 300
   100 200 300
   0 0 0


 위의 예제를 보면 5행에서 배열 a를 선언했다. 그러면 메모리상에서는
(배열 a의 첫 요소의 시작번지는 200으로 가정)
 
  200 a[0]    202 a[1]     203 a[2]         
    100         200           300    

배열로 표현하면            a[0]    a[1]      a[2]
포인터로 표현하면         *(a+0)   *(a+1)    *(a+2)
주소로 표현하면            a+0      a+1      a+2


위에서 컴파일러는 배열이든 포인터이든 아래의 주소로 바꾸어서 본다.
그러니까 a[]는 *a하고 같은 셈이다. 예제18)에서 배열을 선언해주었지만
a가 배열의 첫요소의 시작번지를 가지고 있다는 것을 이용해서 배열로도
포인터 표현으로도 값을 나타냈다. a[0]과 *(a+0)은 같은 표현이다.
 컴파일러의 입장에서보면 같은 주소이니까. 서로 빼면(9행) 0이 된다.
 씨에서는 포인터이든 배열을 적으면 우선 시작번지를 찾는다 위의 예제
에서는 a[0], *(a+0)이든 시작 번지는 a, &a[0]이고, 그다음에는 시작번
지에서 떨어진 거리를 찾는다. a[1], *(a+1)은 a에서 1만큼 떨어졌다. 
떨어진 거리를 offset이라고 하는데 이것은 


 offset = []안의 숫자 * 변수의 타입

으로 구해진다. a[1]의 offset은 int형이니 2이다. 즉 시작번지에서 2바
이트만큼 떨어져 있다는 뜻이다.

 위의 것을 이해하면 이런 표현도 가능하다.

예제20)
1#include 
2
3void main(void)
4{
5    int a[] = { 100, 200, 300 };
6
7    printf("%d %d %d\\n", a[0], a[1], a[2]);
8    printf("%d %d %d\\n", *(a+0), *(a+1), *(a+2) );
9    printf("%d %d %d\\n", *(0+a), *(1+a), *(2+a) );
10    printf("%d %d %d\\n", 0[a], 1[a], 2[a]);
11
12}

결과) 
   100 200 300 
   100 200 300 
   100 200 300
   100 200 300

10행을 보면 배열인 때 배열의 이름하고 []안의 숫자가 바뀌어 있다.
그래도 결과는 7행 과 같다. 왜냐면 8행과 9행이 같기 때문에 
9행을 다시 배열로 적으면 10행이 된다.


 아래의 예제는 어떨까? 

예제21)
1#include 
2
3void main(void)
4{
5    int a[] = { 100, 200, 300 };
6    int *p;
7
8    p = a;
9
10    printf("%d %d %d\\n", a[0], a[1], a[2]);
11    printf("%d %d %d\\n", *(a+0), *(a+1), *(a+2) );
12    printf("%d %d %d\\n", p[0], p[1], p[2]);
13    printf("%d %d %d\\n", *(p+0), *(p+1), *(p+2) );
14
15    printf("%d %d %d\\n", (p+0) - &a[0],
            (p+1) - &a[1],
                         (p+2) - &a[2] );

}

 포인터 p를 선언해주고 p에다 배열 a의 시작 번지를 넣어 주었다(8행)
이렇게 하면 시작번지가 p에 있으므로 (즉 p가 배열 a의 첫요소를 가리키
므로) 간접적으로 배열 a의 값을 얻을 수 있다. 12행처럼 마치 p의 배열
을 선언 해준것처럼 쓸 수도 있다. 이유는 앞에서 한 것과 같다. 
p[0]은 사실 *(p+0)하고 표현이 같기 때문이다. 피씨 통신에서 본 씨
강좌에서 임인건님은 이런 말씀을 하셨다. 

 []은 일종의 연산자이고 a[b]는 *(a+b)하고 같다. 
 
 배열에서 []는 일종의 연산자로 보면 된다. 그래서 위와 같이 변형이 
가능하다.

 포인터와 배열의 미팅부분의 내용을 이해한다면 이차원포인터와 2차원배열의
전환 또 3차원 포인터와 3차원 배열의 전환도 가능하다.
 이것도 해보는게 포인터 이해에 좋을 것 같다.
 하나만 해보자 

 a[1][2]는 

 [2]를 연산자로 보면 *(a[1] + 2)가 되고
 [1]도 연산자이니까 풀면 *( *(a+1) + 2) 가된다.
 
 **** 팁  a[0][0]은?

 *(a[0] + 0)
 *(*(a+0) + 0)
 *(*a + 0)
 **a

위의 네가지 표현은 같다.
 
이것을 잘 몰라서 아이콘 2기 아이들이 무려 3달 가량을 헤맸다. 거기에
나도 포함되어 있다 쩝... (난 바보인가)
 그래서 2차원 배열은 1차원의 확장이니 모 .. 이상한 해석을 다 해보았다
지금 생각해보면 그래도 우리끼리 공부여서 정말 재미있었던 것같다.
아이콘 아이들만큼은 제대로 배워서 학점에.. 그리고 2기를 바탕으로 
좀더 큰 도약(풀그림 짜기)등을 해 보았으면 좋겠다.





**** 끝났음(3일 걸렸다.. 내청춘 돌리도) ***