포인터 스터디

01 14, 2007 01:33


『프로그래밍 언어 강좌-C,C++,VC 강좌 (go PROG)』 1586번
 제  목:[강좌] 포인터 스터디 [1/8] -신경호                         
 올린이:파이사랑(신경호  )    00/01/26 23:56    읽음:619 관련자료 없음
 -----------------------------------------------------------------------------

반 친구들을 위해서 제가 작년에 쓴 자료 입니다.
조잡하고 많이 이상할지도 모르지만, 잘 봐 주세요. ^^;

『배움터-강좌 (go SSCS)』 25번
 제  목:[강좌] 포인터 스터디 [1/8] -신경호
 올린이:파이사랑(신경호  )    00/01/23 23:31    읽음: 11 관련자료 없음
 -----------------------------------------------------------------------------

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ■ 포인터 스터디 [1]                                                  ■
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                                                           [990129 파이사랑]
    ◐ 들어가면서…

      안녕하세요. 몇회에 걸쳐 반게에 올렸던 글입니다. 별로 좋은  자료는 아니
    지만 여기에 있어도  나쁠건 없겠다는 생각에 올리는데,  과연 정말 그럴지는
    모르겠네요. ==; (이 부분만 빼고 나머지는 반게의 글과 동일합니다)

      틀린 내용이 있을때는 가차없이 re를 눌러 저에게 메일을 주세요.

    ◐ 컴파일러에 대해…

      C 강좌이므로 터보 C 2.0을 기준으로 하겠으나 전 볼랜드 C++ 밖에 없기 때
    문에 거기서 테스트 해 본 예제를 올릴 겁니다.  볼랜드 C++ 3.1을 구하고 싶
    으시면 메일 주세요. 꼭 필요한 것들만 압축해서 2메가 정도로 만들어 놓은게
    있습니다.

    ◐ 덧붙이는 글…

      예제는 꼭 실행해 보세요. 그리고 내용이 좀 많아서 읽다가 지루해 하실 분
    도 계실지 모르겠는데, 컴학부라면 당연히 이해해야만 하는 개념입니다. 아무
    래도 글 쓰는게 보는거 보다는 훨씬 힘들터인데 글 쓴 사람  생각도 좀 해 주
    세요. ^^; (머 제가 자처해서 하는 일이지만…)

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ■ 1. 포인터란…                                                      ■
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ◐ 변수와 주소

      우리가 흔히 사용하는 변수. 이 변수의 실체에 대해 얼마나 아시나요? 변수
    를 이름 그대로 해석해 보면 「변할 수 있는 값」이라고 하네요. 그렇지만 값
    이라고 하기에는 조금 무리가 있지요. 하나의 값이 다른 값으로 그냥 바뀔 수
    는 없으니까요. 변수라는 것을  좀 더 구체적으로 말하자면 『값을 저장할 수
    있는 메모리』라고 할 수 있습니다. 이미 아실테지만, 변수는  메모리의 일부
    입니다. 물론 모든 메모리가 변수가 되는건 아니지만  그 가능성은 가지고 있
    지요. 우리가 변수를 선언할 때 컴파일러는 자기가 알아서 비어있는 메모리를
    찾아 그 메모리를 변수로 사용하게 되는 겁니다.

      그렇다면 컴파일러는 어떻게 각 메모리를 구분할까요? 쉽게 생각합시다. 우
    리가 집이나 사람을 구분할 때 바로 주소나 주민등록번호 같은 번호를 사용하
    지요. (주소는 일부만 번호이긴 하지만… 더 쉽게 아파트의 호수를 생각해 보
    세요) 컴퓨터도 마찬가지 입니다. 각각의 메모리에 주소를  붙여 구별을 하는
    것이지요. 그리고 컴퓨터의 메모리는 한줄로 이어져 있기 때문에(이를 「선형
    메모리」라고 합니다) 연속된 값으로 주소값이 표시 됩니다.  즉 컴퓨터의 메
    모리 크기가 100이라고 하면 0부터 시작해서 99까지의 주소가 존재하는  것이
    지요. 그런데 컴퓨터는 일반적으로 수백만 이상의 크기를 가지므로 그만큼 주
    소값의 범위도 커지게 됩니다. 그래서 흔히  주소값은  4바이트의 long형으로
    나타내는 것입니다. (C에서 정수를 표현하는 가장 큰 자료형이지요)

    ◐ 포인터 상수와 변수

      포인터 상수는 별게 아닙니다. (물론 포인터 변수도 별게 아니지만…) 포인
    터 상수는 바로 위에서 말씀드린 주소를 말하는 겁니다. 당연히 크기도 4바이
    트겠지요? 컴퓨터의 가장 첫번째 메모리의  포인터는 0x00000000이 되는 겁니
    다. 그 다음은 0x00000001… 쉽지요? (앞에 0x를 붙인건 16진수라는거 아시지
    요?) 그렇다면 포인터 변수는 무엇일까요?  바로 「포인터 상수를  저장할 수
    있는 변수」겠지요. 포인터 변수의 크기도 포인터 상수처럼 4바이트 입니다.

      char *p; // 포인터 변수를 선언할 때 「*」를 붙이지요

      이 p라는 포인터 변수는 포인터를 저장할 수 있습니다. char의  포인터형이
    긴 하지만 포인터는 포인터 입니다. 앞에 정해진 자료형이 무엇이든 상관없이
    크기는 4바이트인 거지요. 그렇다면 앞에 정해진 자료형은 왜 붙여주는  것일
    까요? 흠… 이건 나중에 알아보도록 하고, 그림을 몇개 보도록 하지요.

      char ch = 'A';  // 'A'의 코드 값은 65 입니다
      char *pc = &ch; // 변수의 주소값을 알아낼 때는 「&」를 사용합니다
                      // ch의 주소값이 0x0000001A라고 가정합시다
    ┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃    ┃       ┏━━━━━━━━━━━━━━━━┓                   ┃
    ┣━━┫       ▼                                                     ┃
    ┃주소┃   10  1A  1B  1C  1D  1E                21  22  23  24  25   ┃
    ┃    ┃ ┳━┳━┳━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
    ┃ 값 ┃ ┃  ┃65┃  ┃  ┃  ┃  ┃    …      ┃1A┃00┃00┃00┃  ┃ ┃
    ┃    ┃ ┻━┻━┻━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
    ┃이름┃       ch                                pc                   ┃
    ┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      컴퓨터가 수를 저장하는 방법은 좀 특이 합니다.  바이트 별로 거꾸로 저장
    하는 것이지요. 즉, 0x12345678을 첫번째 바이트에 78,  두번째 바이트에 56,
    세번째 바이트에 34, 네번째 바이트에 12를 저장하는 것이지요.  이유는 조금
    후에 알아보도록 하겠습니다.

      한가지 더. 이 글에 사용되는 모든 주소값은 임의로 붙인 것입니다. 컴파일
    러가 마음대로 빈 공간을 찾아 사용하기 때문에 특별히 정해진 값이 있을수가
    없기 때문이지요.

      int ih = 0x13;
      int *pi = &ih;
    ┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃주소┃   41  42  43  44  45  46                5A  5B  5C  5D  5E   ┃
    ┃    ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
    ┃ 값 ┃ ┃  ┃13┃00┃  ┃  ┃  ┃    …      ┃42┃00┃00┃00┃  ┃ ┃
    ┃    ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
    ┃이름┃       ih                                pi                   ┃
    ┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      long lh = 0x00781253;
      long *pl = &lh;
    ┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃주소┃   65  66  67  68  69  70                7A  7B  7C  7D  7E   ┃
    ┃    ┃ ┳━┳━━━━━━━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
    ┃ 값 ┃ ┃  ┃53┃12┃78┃00┃  ┃    …      ┃66┃00┃00┃00┃  ┃ ┃
    ┃    ┃ ┻━┻━━━━━━━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
    ┃이름┃       lh                                pl                   ┃
    ┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

    ◐ *연산자의 쓰임새

      모든 연산자가 그렇듯이 *와 &도 여러가지 쓰임새를 가지고 있습니다. 우선
    *는 곱을 구하기도 하고 위에서 사용된 것 처럼 자료형에 붙어 포인터 변수임
    을 나타내기도 합니다.  (이 경우는 연산자라고 보기는 좀 어렵지요)  그리고
    또 한가지. 「주어진 주소의 값을 읽어내는」 일도 합니다.  다음 프로그램을
    실행해 보세요.

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃  #include <stdio.h>                                                ┃
    ┃                                                                    ┃
    ┃  void main(void) {                                                 ┃
    ┃      int i;                                                        ┃
    ┃      for (i = 0; i < 100; i++)                                     ┃
    ┃          printf("%c", *(char *)i);                                 ┃
    ┃  }                                                                 ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      실행해 보면 화면이 이상한 문자들만 잔뜩 나올 겁니다. 그럼  이 문자들이
    뜻하는게 무엇일까요? 소스 코드를 한번 봅시다. i 값이 0부터 99까지 증가할
    테고 printf 문에서 무언가를 출력하라고 하지요. 그 부분만 뜯어보면,

      *(char *)i

      i가 0인 경우 위의 코드는 다음과 같겠지요.

      *(char *)0

      이게 뜻하는 의미를 아시겠나요? 우선 0이라는 수(상수)를 char *형으로 캐
    스팅 했지요. 이제 0은 그냥 수가 아닌 char 크기(1 바이트)의 메모리를 나타
    낼 수 있는 포인터 상수가 된 겁니다. 그리고 거기에 *연산자를 붙여 값을 읽
    어내고 있지요. 그렇습니다. 이 소스 코드는  0번 메모리의 값을 읽어서 출력
    하는 것이지요. 이렇게 99번째 메모리까지  모두 100개의 메모리의 내용을 보
    여주는 코드인 것입니다. 물론 우리가 알아볼 수는 없는 글자들만  잔뜩 있지
    만요.

      만약에 (char *)로 캐스팅을 안하고 *0이라고만 쓰면 어떻게 될까요?  에러
    가 나지요? 왜일까요? *연산자는  그 다음에 오는 내용을  포인터라고 가정을
    하기는 하는데, 거기서 얼마만한 크기의 메모리를 읽어와야  하는지를 모르는
    겁니다. 즉 *앞의 자료형은 「그 포인터의 값을 *를 사용해서 읽어올 때 얼마
    만한 크기를 읽어와야 하는지를 정해주는 것」이라 할 수 있습니다.  다음 예
    제를 보세요.

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃  #include <stdio.h>                                                ┃
    ┃                                                                    ┃
    ┃  void main() {                                                     ┃
    ┃      int ih = 0x0506;                                              ┃
    ┃      char *pp = (char *)&ih;                                       ┃
    ┃                                                                    ┃
    ┃      long lh = 0x01020304;                                         ┃
    ┃      char *pc = (char *)&lh;                                       ┃
    ┃      int *pi = (int *)&lh;                                         ┃
    ┃                                                                    ┃
    ┃      printf("- integer -\n");                                      ┃
    ┃      printf("ih=%X\n", ih);                                        ┃
    ┃      printf("(char)ih=%X, *(char *)=%X\n", (char)ih, *pp);  …①   ┃
    ┃                                                                    ┃
    ┃      printf("\n- long integer -\n");                               ┃
    ┃      printf("lh=%lX\n", lh);                                       ┃
    ┃      printf("(char)lh=%X, *(char *)=%X\n", (char)lh, *pc);  …②   ┃
    ┃      printf("(int)lh=%X, *(int *)=%X\n", (int)lh, *pi);     …③   ┃
    ┃  }                                                                 ┃
    ┃                                                                    ┃
    ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
    ┃  <결과>                                                            ┃
    ┃  - integer -                                                       ┃
    ┃  ih=506                                                            ┃
    ┃  (char)ih=6, *(char *)=6                                           ┃
    ┃                                                                    ┃
    ┃  - long integer -                                                  ┃
    ┃  lh=1020304                                                        ┃
    ┃  (char)lh=4, *(char *)=4                                           ┃
    ┃  (int)lh=304, *(int *)=304                                         ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      ①의 첫번째 값은 int형 0x0506을 char형으로 캐스팅한 값입니다. 당연하게
    도 int는 2바이트, char형은 1바이트이므로 char형을 벗어나는 부분이 잘려져
    나간 6이 결과가 되겠지요.

      포인터를 사용한 연산도 마찬가지 입니다. pp는  1바이트 크기의 변수를 포
    인트하는 포인터 변수이지요. 이 pp 변수를 사용해 그 메모리를  읽어오게 되
    면 그 첫번째 바이트 한 바이트만을 읽어오게 되는 것이지요.

    ┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃주소┃   41  42  43  44  45  46                5A  5B  5C  5D  5E   ┃
    ┃    ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
    ┃ 값 ┃ ┃  ┃06┃05┃  ┃  ┃  ┃    …      ┃42┃00┃00┃00┃  ┃ ┃
    ┃    ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
    ┃이름┃       ih                                pp                   ┃
    ┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      pp는 char *형 변수이므로 *pp는 char 만큼(1바이트)만을  읽어오게 된다는
    것입니다. ih를 char형으로 캐스팅한 결과와 같게 되지요. 당연히  같아야 하
    는 거고요.

      만약 수를 거꾸로 저장하지 않고 순서대로 저장을 한다면 두 결과가 틀리게
    나올 겁니다. 포인터를 사용해 어떤 변수의 일정 크기만큼만을 읽어오려고 할
    때 문제가 생기겠지요. 굉장히 복잡한 과정이 요구될 겁니다. 이 때문에 컴퓨
    터는 수를 거꾸로 저장하는 것이지요.

      ②와 ③은 위의 내용과 같은 내용이므로 넘어가도록 하겠습니다.

    ◐ &연산자의 쓰임새

      연산자 &는 비트 연산자로써 AND 연산을 행하는 연산자입니다. 그런데 여기
    서는 address-of 연산자의 기능도 행하고 있습니다. 이름 그대로 「어떤 변수
    의 주소를 알아내는」 역할을 하는 연산자 입니다. 상수는 메모리에 위치하지
    않으므로 주소가 있을 수 없고 당연히 &연산자도 사용하지 못하겠지요.

    ◐ *연산자와 &연산자의 관계

      *연산자는 어떤 주소의 값을 읽어오는 연산자라고 했고 &연산자는  어떤 변
    수의 주소를 알아내는 연산자라고 했지요. 이 두 연산자의  기능은 완전히 반
    대라고 할 수 있겠지요. 물론 변수에 대해 사용할 때 말입니다.  (상수에서는
    *는 가능하지만 &는 아예 사용을 하지 못하지요)

    ◐ 포인터 변수를 사용해 간접적으로 변수의 값 참조하기

      int ih = 0x1234;
      int *pi = &ih;
      *pi = 0x5678;

      이미 아시겠지만 간단히 설명하겠습니다. pi는 ih의 주소를 가지고 있고 그
    주소의 메모리를 *연산자를 통해 참조하고 있습니다. 그 메모리에 0x5678이라
    는 값을 넣고 있지요. 한 문장으로 설명한다면 「pi의 값을 주소로 하는  2바
    이트 크기의 메모리에 0x5678를 대입하는」 것입니다. 다음과 같이 한다면 어
    떻게 될까요?

      int ih = 0x1234;
      char *pc = &ih;
      *pc = 0x56;

      그렇습니다. 바로 ih 값중의 char형으로 참조 가능한 부분인 첫번째 바이트
    만이 바뀌게 되지요. 결과적으로 ih는 0x1256이 되는 것입니다. 신기하지요?

    ◐ void형 포인터

      포인터 변수를 선언할 때 * 앞에는 자료형을 정해준다고 했습니다. 만약 다
    음과 같이 사용한다면 어떻게 될까요?

      int ih = 0x1234;
      void *vc = (void *)&ih;

      물론 아무런 에러도 나지 않습니다. vc도 포인터 변수이므로 당연하게도 ih
    변수의 포인터를 저장할 수가 있는 것입니다. 다음은 어떨까요.

      *vc = 0x5678;

      이 코드도 에러가 나지 않을까요? 조금만 생각해 보면 알 수 있지요.  당연
    히 에러가 발생 합니다. void형은 크기를 갖지 않기 때문에 얼마만한 크기 만
    큼의 메모리에 값을 넣어야 할지를 모르는 거지요.  다음과 같이 사용하면 되
    겠지요.

      *(int *)vc = 0x5678;

      이렇듯 void형 포인터는 어떤 포인터 값도 가질 수 있지만,  직접적으로 참
    조를 할 수는 없고 반드시 캐스팅을 해 주어야 하는 포인터 입니다.  별로 사
    용될 곳이 없어 보이기도 하지만 나중에 프로그래밍을 하다보면  의외로 쓰이
    는 곳이 많기도 하지요.

    ◐ 참조에 의한 호출 (참조 호출, Call by reference)

      우선 Call by value를 알아 봅시다. 우리가 일반적으로 사용하는 함수 호출
    방법이라고 들었지요? 다음을 봅시다.

      int i = 3;

      printf("%d", 3); //…①
      printf("%d", i); //…②

      ①과 ②는 어떻게 다를까요? 우리가 보기에는 좀 달라 보이지만,  컴파일러
    가 컴파일을 마친 이후에는 완전히 동일한 코드가 됩니다. 다시 말해서  ②번
    의 경우 i 대신에 i가 가진 값인 3을 대치시켜서 넘겨준다는 것이지요.  바로
    이것이 Call by value 입니다. 변수가 넘어가는 것이  아닌 변수가 가지고 있
    는 「값」이 넘어가기 때문이지요.

      그렇다면 Call by reference는 무엇일까요? Call by value와 비교해 생각해
    봅시다. (이 예제 또 쓰게 되네요… ==;)

      void swapA(int a1, int a2) {
          int ta;
          ta = a1;
          a1 = a2;
          a2 = ta;
      }

      void swapB(int *b1, int *b2) {
          int tb;
          tb = *b1;
          *b1 = *b2;
          *b2 = tb;
      }

      흔히 보던 함수지요? 이 함수는 다음과 같이 사용합니다.

      int i = 3, j = 4;
      swapA(i, j);   //…③
      swapB(&i, &j); //…④

      이후의 결과는 아시다시피 i와 j값이 바뀌어 있겠지요. (swapB 함수만 제대
    로 동작하니까요) 그럼 내부적으로 어떤 과정을 통하길래 이런 것이 가능할까
    요? 그림을 보도록 하지요. (그림 크기의 한계상 포인터와 int형의 크기를 모
    두 1이라고 하겠습니다)

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃③-1 초기 상태 (i, j 선언과 정의)                                   ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃  ┃  ┃  ┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                                        ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃③-2 [swapA(i, j)] = [swapA(3, 4)]                                  ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃ 3┃ 4┃  ┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       a1  a2           ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃③-3 [int ta = a1] = [int ta = 3]                                   ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃ 3┃ 4┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       a1  a2  ta       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃③-4 [a1 = a2] = [a1 = 4]                                           ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃ 4┃ 4┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       a1  a2  ta       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃③-5 [a2 = ta] = [a2 = 3]                                           ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃ 4┃ 3┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       a1  a2  ta       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃③-6 swapA 함수 종료 (swapA의 변수가 없어짐)                        ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃ 4┃ 3┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                                        ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      결국 이 함수는 swapA의 두개의 인자의 값만 바꿔주는 역할을 했지  호출한
    쪽의 i, j 변수의 값을 바꾸지는 못했습니다.  (변수가 없어진다는 것은 이름
    이 없어진다는 것을 말합니다.  해당 메모리의 값이  0으로 되거나 하는 것은
    아닙니다) 그럼 swapB 함수를 보도록 하지요.

    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃④-1 초기 상태 (i, j 선언과 정의)                                   ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃  ┃  ┃  ┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                                        ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃④-2 [swapB(&i, &j)] = [swapB(30, 31)]                              ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃30┃31┃  ┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       b1  b2           ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃④-3 [int tb = *b1] = [int tb = *30] = [ing tb = i] = [int tb = 3]  ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 3┃ 4┃  ┃  ┃…   …┃  ┃30┃31┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       b1  b2  tb       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃④-4 [*b1 = *b2] = [*30 = *31] = [i = j] = [i = 4]                  ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 4┃ 4┃  ┃  ┃…   …┃  ┃30┃31┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       b1  b2  tb       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃④-5 [*b2 = tb] = [*31 = tb] = [j = tb] = [j = 3]                   ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 4┃ 3┃  ┃  ┃…   …┃  ┃30┃31┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                       b1  b2  tb       ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┃                                                                    ┃
    ┃④-6 swapB 함수 종료 (swapB의 변수가 없어짐)                        ┃
    ┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
    ┃┃주소      28  29  30  31  32  33           79  80  81  82       ┃┃
    ┃┃      ━┳━┳━┳━┳━┳━┳━┳━   ━┳━┳━┳━┳━┳━   ┃┃
    ┃┃ 값   …┃  ┃  ┃ 4┃ 3┃  ┃  ┃…   …┃  ┃30┃31┃ 3┃…   ┃┃
    ┃┃      ━┻━┻━┻━┻━┻━┻━┻━   ━┻━┻━┻━┻━┻━   ┃┃
    ┃┃이름               i   j                                        ┃┃
    ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      아시겠지요? b1, b2 포인터를 사용해 i, j의 값을 직접 바꾸는 결과가 나타
    났습니다. 다음을 한번 볼까요.

      void swapC(long c1, long c2) {
          int temp;
          temp = *(int *)c1;
          *(int *)c1 = *(int *)c2;
          *(int *)c2 = temp;
      }

      어떤 생각이 드세요? swapB 함수와 이 함수가 다를까요? 같을까요? 이미 예
    상을 하셨겠지만(같은거니까 물어보겠죠? ^^) 완전히 동일한 결과를 나타냅니
    다.

      어떻게 이렇게 사용이 가능할까요? swapB 함수에서  가장 중요한 부분이 뭔
    지 아세요? 인수로 포인터를 받는다고요? 아닙니다.  인수의 타입은 포인터이
    든 아니든 간에 4바이트의 메모리를 가질 수 있는 크기라면 뭐든지 가능 합니
    다. int형이 4바이트인 컴퓨터에서는 long이 아닌 int형도 가능하다는 것이지
    요. 가장 중요한 부분은 바로 인수로 받은 두 값을 참조할 때 *연산자를 사용
    한다는 겁니다.  사실상 변수 선언시에 쓰이는 *는  그렇게 큰 의미가 없다는
    거지요. 때문에 void *형은 long형과 동일하다고 생각하셔도 됩니다.  포인터
    값을 가질수는 있지만 캐스팅 없이는 참조가 불가능한…  아셨죠?  (포인터는
    4바이트 크기의 상수라는 것만 확실히 아신다면 어려울게 없지요)

      이렇게 인자로 받은 두 값을 참조할 때는 그걸 포인터로 가정하고  그 크기
    를 정해주기 위해서 int *형으로  캐스팅을 하지요.  이렇게 캐스팅된 포인터
    상수에서부터 값을 참조하는 *연산자가 이 함수의 핵심이지요.

      그리고 ④-2를 보면 swapB(&i, &j)가 swapB(30, 31)로 바뀐다고  나와 있지
    요. 그렇습니다. 이것이 바로 Call by reference이지만 이것도 사실상은 포인
    터의 값(!)만을 넘겨주는 Call by value인 것이지요.

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ◐ 첫번째 스터디가 끝났습니다. 다음 스터디를 기대해 주세요. 이해가 안 되
       시는 내용이나 궁금한 사항이나 하여튼 아무 말이라도 하고 싶으시면 언제
       라도 메일 주세요. 다음 시간에는 말씀드린대로 문자열과 포인터에 대해서
       하겠습니다. 곧 C++ 스터디도 할까 합니다…

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

『프로그래밍 언어 강좌-C,C++,VC 강좌 (go PROG)』 1587번
 제  목:[강좌] 포인터 스터디 [2/8] -신경호                         
 올린이:파이사랑(신경호  )    00/01/26 23:57    읽음:229 관련자료 없음
 -----------------------------------------------------------------------------


『배움터-강좌 (go SSCS)』 27번
 제  목:[강좌] 포인터 스터디 [2/8] -신경호
 올린이:파이사랑(신경호  )    00/01/23 23:35    읽음:  3 관련자료 없음
 -----------------------------------------------------------------------------

    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ■ 2. 배열과 포인터                                                   ■
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ◐ C에서의 문자열

      C에서 문자열은 포인터를 사용해서 구현된다는 것은 다 아실겁니다. 정말로
    그런지 한번 살펴보도록 하지요. 다음 문장을 보세요.

      char *sp = "Love";

    ┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃주소┃   41  42  43  44  45  46                5A  5B  5C  5D  5E   ┃
    ┃    ┃ ┳━┳━┳━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
    ┃ 값 ┃ ┃  ┃L ┃o ┃v ┃e ┃\0┃     …     ┃42┃00┃00┃00┃  ┃ ┃
    ┃    ┃ ┻━┻━┻━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
    ┃이름┃                                         sp                   ┃
    ┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

      제가 항상 포인터를 그 포인터가 가리키는 메모리 영역보다  뒤에 그리는데
    반드시 그런건 아닙니다. 포인터 변수도 역시 변수이기 때문에 컴파일러가 알
    아서 비어있는 곳에 메모리를 할당하는 것이지요.

      위의 예제를 보면 그 동안의 예제와는 다르게 "Love"의  첫 부분에 따로 이
    름이 붙어있지 않지요? 바로 그렇습니다. C에서는 문자열 자체에 이름을 붙일
    수는 없는 거지요. 그 포인터를 통해서만 참조가 가능한 겁니다. 다시 말씀드
    려서 C에서의 문자열은 포인터가 전부라는 얘깁니다. 그러니까 문자열의 길이
    도 일반적인 방법으로는 알아낼 수가 없지요.  따로 길이를  저장하는 공간이
    없으니까요.

      그래서 C에서 사용하는 방법이 바로 문자열 끝에 0이라는 값을 넣어주는 것
    입니다. 여기서 0이란 것은 코드값이지 문자로 표현되는 '0'이 아닙니다.  둘
    이 어떻게 다른건지는 아시겠지요? (문자 '0'의 코드는 0x30 입니다,  10진수
    로는 48이지요)

      결국 문자열 중간에 0이라는 코드를 가지는 문자는 넣을 수 없게 되겠지요?
    그러나 걱정할 것 없습니다. 코드 0은 사용되지 않는 문자이기 때문이지요.

      그럼 이 문자열을 어떻게 다루지요?
      puts(sp);

      이렇게하면 문자열을 출력하는게 되지요? 그런데 이상한 점이 있지요. 여태
    까지는 포인터가 가리키는 내용을 출력할려면 *연산자를 붙여야 했었는데  여
    기서는 그러질 않네요. 왜 그럴까요? 그냥 당연히 그런거다 생각하시나요? ^^

      그렇지는 않지요. 사실상은 puts 함수 안에서 인자로 받은 포인터에 *를 붙
    여서 내부적으로 한 문자씩 출력하기 때문이지요. 문자열을  다루는 함수들이
    내부적으로 어떤 과정을 거치는지는 잠시 후에 알아보기로 합시다.

    ◐ 문자열과 배열

      아시다시피 배열과 포인터는 매우 가까운 관계 입니다. 그러니 당연히 배열
    과 문자열도 밀접한 관계를 가지고 있겠지요. 우리는  배열의  참조 연산자인
    []를 사용해서 문자열에도 접근 할 수 있었습니다.

      sp[0]은 'L'이고 sp[1]은 'o'겠지요. 이 정도는 아시리라 믿고…

   

브니 Programs/C++

01 14, 2007 01:33 01 14, 2007 01:33