Programming - cpueblo.com

C Pointer and Arrays


글쓴이 : 유광희 날짜 : 2002-05-13 (월) 21:21 조회 : 7556
『시지프스(Cezips) C/C++프로그래밍-C/C++ 강좌 (go CEZIPS)』 677번
 제  목:[강좌] C Pointer and Arrays #8                              
 올린이:adonrs  (이원철  )    98/07/25 11:38    읽음:519 관련자료 없음
 -----------------------------------------------------------------------------

 포인터와 배열(Pointer and Arrays)에 대해서 알아봅시다.

 1. 포인터의 기초

 ▣ 포인터와 번지

  일반적으로 변수에 대입되는 자료는 메모리(주기억장치)에 저장된다. 컴퓨터
  에서 메모리에 저장된 자료를 참조하기 위해서는 반드시 자로가 저장된 위치
  를 표시하는 번지(또는 주소, address)를 지정하여 참조한다. 즉, 컴퓨터 내
  부에서는 메모리에 저장된 자료를 참조하기 전에 자료가 저장된 번지를 먼저
  지정해야 한다.

               메모리         
          │     :     │
          ├──────┤
  1000번지│     97     │
          ├──────┤
  1001번지│     127    │
          ├──────┤
  1002번지│     82     │
          ├──────┤
          │     :     │

  위 그림의 메모리에서 자료 97값을 참조하기 위해서는 메모리의 번지 1000을
  지정해야 한다.

  프로그램 내에서 선언된 변수는 그 자료형의 크기에 따라 컴파일러에 의하여
  고정된 메모리에 할당된다. 즉, 변수의 자료값을 저장하기 위해서 할당된 메
  모리의 번지는 컴파일러에 의하여 지정되어 고정되며 변경할 수 없게 된다.
  이에 반하여 포인터는 자료가 저장된 메모리의 번지를 프로그램내에서 직접
  지정이 가능하며 번지의 지정에 의해 자료를 참조한다. 즉, 포인터는 자료의
  값이 아닌 번지로서 참조하는 변수로 볼 수 있다. 예를 들어 다음과 같은 C
  프로그램의 예를 살펴보자.(변수 c는 컴파일어에 의해 메모리 1001번지에 할
  당되었다고 가정한다.)

  char c;
  char *p;     …p는 포인터이다.

  c = 121;     …121은 문자 'y'의 ASCII 값이다.
  p = &c;      …p에 c의 번지. 즉 1001번지가 대입된다.

  요약하면, 일반변수는 컴파일러에 의해 번지가 고정되며 사용자가 변경할 수
  없으며 자료의 값에 의해서만 참조된다. 포인터는 사용자가 번지를 직접 지시
  하여 변경할 수 있으며 자료의 값도 간접지시로서 변경할 수 있다.

 ▣ 포인터 연산자

 ┌───┬────────────────────────────────┐
 │  &   │변수의 번지를 나타낸다.                                         │
 ├───┼────────────────────────────────┤
 │  *   │포인터가 지시하는 자료의 값을 나타낸다.(포인터에 의한 간접지시) │
 └───┴────────────────────────────────┘

  포인터 연산자는 다음과 같은 형태로 사용된다.

  포인터 = &(변수);    …변수의 주소를 포인터에 대입한다.
  변수 = *(포인터);    …포인터가 지시하는 자료의 값을 변수에 대입한다.

  포인터의 선언 : 자료형  *포인터명;

  [포인터 연산자 사용 예]

  int x = 1, y = 2, z[10];
  int *ip;    …int형 포인터 ip선언

  ip = &x;    …ip에 x의 번지 대입(ip는 x를 가리킨다)
         ip                    x          y
      ┌──┐              ┌──┐   ┌──┐
  ⇒  │ · ┼──────→│ 1  │   │ 2  │
      └──┘              └──┘   └──┘
         &x                    *ip

  y = *ip;    …ip가 가리키는 곳의 내용(x)을 y에 대입한다. y는 1이 된다.
         ip                    x          y
      ┌──┐              ┌──┐   ┌──┐
  ⇒  │ · ┼──────→│ 1  │   │ 1  │
      └──┘              └──┘   └──┘

  *ip = 0;    …ip가 가리키는 곳의 내용(x)에 0을 대입한다. x는 0이 된다.
         ip                    x          y
      ┌──┐              ┌──┐   ┌──┐
  ⇒  │ · ┼──────→│ 0  │   │ 1  │
      └──┘              └──┘   └──┘

  ip = &z[2]; …ip는 z[2]를 가리킨다.
         ip                  z[0]z[1]z[2]      z[9]
      ┌──┐              ┌─┬─┬─┬──┬─┐
  ⇒  │ · ┼┐            │  │  │  │……│  │
      └──┘│            └─┴─┴─┴──┴─┘
              │                      ↑
              └───────────┘

  포인터 연산자의 사용을 요약하면 아래표와 같다.

 ┌────┬────────┬──────┐
 │ 선 언  │ 자료의 값 참조 │ 번지 참조  │
 ├────┼────────┼──────┤
 │ int a; │       a        │     &a     │
 ├────┼────────┼──────┤
 │ int *p;│       *p       │     p      │
 └────┴────────┴──────┘

 ▣ 포인터의 값 설정

  일반적으로 포인터에 대입되는 번지의 값은 컴파일러가 확보한 메모리의 번지
  를 설정한다. 예를 들어 다음과 같이 포인터에 값을 대입한 경우에는 컴파일
  시에 경고 오류 메세지가 출력된다.

  int *ip;
  *ip = 100;

  즉, 포인터 ip가 컴파일러에 의해 할당된 메모리를 가리키지 않고 임의의 의미
  없는 번지에 자료값을 대입할 위험이 있어 통상 컴파일 시에 「포인터 ip가 초
  기화 되지 않음」이라는 오류 메세지가 출력된다.
  또한, ip = 0xFF0B; 와 같이 직접 수치값을 지정하여도 컴파일러에 의해 확보
  되지 않는 의미없는 번지가 될 위험이 있다. 따라서 포인터에는 컴파일러가 확
  보한 의미있는 번지를 설정하여 대입하여야 한다.
  포인터에 부득이 절대 번지값을 수치로서 대입하고자 하는 경우에는

  ip = (int *)0xFF0B;

  와 같이 캐스트연산자를 사용하여 원하는 포인터형의 형변환을 지시하여야 한
  다. 포인터에 번지를 설정하는 대표적인 방법은 다음 예와 같다.

  [포인터 번지 설정 예]

  (1) 변수의 번지를 &연산자로 대입한다.

      int d;
      int *p;
      p = &d;

  (2) 배열의 시작번지를 대입한다. 배열명 s는 s[0] ∼ s[79]의 시작번지를 나
      타내는 포인터 상수로서 &s와 같이 지정할 필요가 없다.

      char s[80];
      char *p;
      p = s;

  (3) 배열의 한 요소의 번지를 &연산자로 대입한다.

      char s[80];
      char *p;
      p = &s[4];

  (4) char형 포인터에 문자열의 시작번지를 설정한다. 여기서, "abcd"의 문자
      열이 저장된 메모리는 컴파일러가 확보하고 그 시작번지를 p에 대입한다.

      char *p;
      p = "abcd";

  (5) 포인터에 NULL( 화일에 0으로 정의되어 있음)을 대입한다. 0의
      절대수치 대입은 특별히 허용되어 포인터가 의미있는 번지를 가리키지 않
      고 있다는 특별한 상태를 표시한다. 예를들어 fopen() 함수는 화일을 개
      방시키는 함수로서 지정된 화일을 개방할 수 없는 경우에 NULL이 반환되
      며 오류발생을 식별하기 위해 사용된다.

      FILE *fp;   …화일을 지정하는 포인터
      if((fp = fopen("myfile.dat","r")) == NULL) ...

  (6) 프로그램 실행 시 동적으로 메모리를 할당하고 그 시작번지를 대입한다.

      char *p;
      p = (char *) malloc(100);   …100바이트를 확보하고 그 시작번지를
                                    p에 대입한다.

 ▣ 포인터의 초기화

  포인터도 일반 변수처럼 선언과 동시에 초기화할 수 있다.

  포인터의 초기화 : 자료형  *포인터명 = 초기치 번지;

  초기화되는 번지는 통상 컴파일러가 확보한 의미있는 번지가 포인터 선언시
  대입된다. 초기화의 예는 다음 예와 같다.

  [포인터 초기화 예]

  (1) 변수의 번지를 & 연산자로 대입한다.

      int d;
      int *p = &d;

  (2) 배열의 시작번지를 대입한다.

      char s[80];
      char *p = s;

  (3) 배열의 한 요소의 번지를 & 연산자로서 대입한다.

      char s[80];
      char *p = &s[5];

  (4) char형 포인터에 문자열의 시작번지를 설정한다. 우선 문자열 "abcd"를
      모리 상의 임의의 번지에 할당하고 그 시작번지를 p에 대입한다. 문자열
      의 값 "abcd"가 p에 대입되는 것은 아니다.

      char *p = "abcd";

  (5) 프로그램 실행 시 동적으로 메모리를 확보하고 그 시작번지를 p에 대입
      한다.

      char *p = (char *) malloc(100);

 2. 배열

 ▣ 1차원 배열

  배열은 여러개의 자료를 메모리에 연속적으로 저장하기 위해 사용된다. 배열
  의 한 요소의 참조는 배열명과 요소의 순서를 나타내는 인덱스로써 참조하게
  되며 컴파일시에 배열의 크기만큼 메모리가 확보된다. 예를 들어 char형 배
  열 ss[5]는 메모리에 5개의 바이트가 다음과 같이 할당되고 참조된다.(ss가
  멤리 1000번지 부터 할당되었다고 가정한다.)

  char ss[5];

          │            │
          ├──────┤
  1000번지│            │ss[0]    …인덱스는 0부터 시작된다.
          ├──────┤
  1001번지│            │ss[1]
          ├──────┤
  1002번지│            │ss[2]
          ├──────┤
  1003번지│            │ss[3]
          ├──────┤
  1004번지│            │ss[4]
          ├──────┤
          │            │

  배열의 한 요소의 크기는 선언되는 자료형의 크기만큼 확보된다. 예를들어 정
  수형의 크기가 2바이트 일 때 int형 배열 n[3]은 6바이트를 다음과 같이 확보
  한다.(n이 메모리 200번지 부터 할당되었다고 가정한다.)

  int n[3];

         │            │
         ├──────┤
  200번지│            │┐        …배열요소 1개당 2바이트를 차지한다.
         ├──────┤│n[0]
  201번지│            │┘
         ├──────┤
  202번지│            │┐
         ├──────┤│n[1]
  203번지│            │┘
         ├──────┤
  204번지│            │┐
         ├──────┤│n[2]
  205번지│            │┘
         ├──────┤
         │            │

 ▣ 포인터와 배열

  C 언어에서는 포인터와 배열은 거의 같은 의미로 해석되며 서로 혼합하여 사
  용되어도 관계 없다. 즉, 배열명은 배열의 시작을 가리키는 번지를 갖고 있는
  포인터 상수로서 해석된다. 단지 배열은 컴파일 시에 모든 배열요소가 저장될
  메모리가 확보되지만 포인터는 단지 하나의 번지가 저장될 메모리만을 확보한
  다. 따라서 일반적으로는 배열을 선언하여 메모리를 확보하고 포인터를 배열
  의 시작번지로 초기화하여 사용한다.

  [배열과 포인터의 관계]

  char ss[5];
  char *p = ss;

  ⇒ char형 배열 ss[5]의 선언으로 메모리 5바이트가 컴파일시 확보된다. 여기
     서 배열명 ss는 배열의 시작번지를 나타내는 포인터 상수가 되어 포인터 p
     는 배열의 시작번지를 가리킨다. 배열 ss가 1000번지 부터 할당되었다고
     가정할 때 배열과 포인터의 관계는 아래그림과 같다.

      p             │            │
  ┌──┐          ├──────┤
  │1000┼→1000번지│            │ss[0], *p,     *ss,     p[0]
  └──┘          ├──────┤
            1001번지│            │ss[1], *(p+1), *(ss+1), p[1]
                    ├──────┤
            1002번지│            │ss[2], *(p+2), *(ss+2), p[2]
                    ├──────┤
            1003번지│            │ss[3], *(p+3), *(ss+3), p[3]
                    ├──────┤
            1004번지│            │ss[4], *(p+4), *(ss+4), p[4]
                    ├──────┤ └───────────┘
                    │            │     모두 같은 표현이다.

  [포인터와 배열명의 대입관계]

  char ss[5];
  char *p;

  p = ss;     …포인터 p에 배열 ss의 시작번지를 대입한다.
                배열명 ss는 배열의 시작번지를 의미하는 포인터 상수이다.

  ss = p;     …오류가 발생한다.
                배열명 ss는 상수로서 취급되며 번지값을 변경할 수 없다.

  p = ss + 3; …p에 ss[3]의 번지를 대입한다.
                즉, p = &ss[3]과 같다.

  [포인터와 배열의 관계 예 1]

  #include 
  main()
  {
    int num[5] = {15, 36, 270, 69, 57};
    int i;

    for(i = 0; i < 5; i++)
       printf("%d", num[i]);
    printf("\\n");
    for(i = 0; i < 5; i++)
       printf("%d", *(num+i));
    printf("\\n");
  }

 【실행 결과】 15 36 270 69 57
               15 36 270 69 57

  [포인터와 배열의 관계 예 2]

  #include <stdio.h>
  main()
  {
    char ss[] = "abcd";    ←①배열 초기화
    char *p = ss;          ←②포인터의 초기화
    int i;

    for(i = 0; i <= 3; i++)
       putchar(ss[i]);
    putchar('\\n');
    for(i = 0; i <= 3; i++)
       putchar(p[i]);
    putchar('\\n');
  }

 【실행 결과】 abcd
               abcd
 【설     명】 ·①배열 ss는 문자열의 크기만큼 선언되고 초기화 된다.
                 ('\\0' 포함하여 5바이트 확보)
                   ss[0] ss[1] ss[2] ss[3] ss[4]
                 ┌──┬──┬──┬──┬──┐
                 │'a' │'b' │'c' │'d' │'\\0'│
                 └──┴──┴──┴──┴──┘
               ·②에서 p는 초기화 되어 배열 ss의 시작 번지를 가리킨다.
                                   ss[0] ss[1] ss[2] ss[3] ss[4]
                 ┌──┐        ┌──┬──┬──┬──┬──┐
                p│ · ┼───→│'a' │'b' │'c' │'d' │'\\0'│
                 └──┘        └──┴──┴──┴──┴──┘

  [1차원 배열의 인수 전달 예]

  #include <stdio.h>
  void sum1(int dt[], int n);
  void dum2(int *dt, int n);

  main()
  {
    int a[5] = {1, 2, 3, 4, 5};

    sum1(a, 5);
    sum2(a, 5);
  }

  void sum1(int dt[], int n)
  {
    int s = 0, i;
    for(i = 0; i < n; i++)
       s += dt[i];
    printf("sum = %d\\n", s);
  }

  void sum2(int *dt, int n)
  {
    int s = 0, i;
    for(i = 0; i < n; i++, dt++)     ←①
       s += *dt;
    printf("sum = %d\\n", s);
  }

 【실행 결과】 sum = 15
               sum = 15
 【설     명】 ·배열을 인수로 전달하는 경우 call by reference로서 배열의
                 시작번지가 전달된다.
               ·①에서 dt++는 배열의 요소를 가리키는 포인터가 다음 요소를
                 가리키도록 증가한다. 이 경우 정수가 2바이트의 크기로 표현
                 된다면 dt++는 2바이트가 증가한다.

 ▣ 다차원 배열

  C 언어에서는 1차원 배열의 요소가 배열이 되는 다차원 배열을 사용할 수 있
  다. 다차원 배열의 예는 아래와 같다.

  char a[2][3];      ←2차원 배열
  int d[4][5][8];    ←3차원 배열

  위 예에서 a는 2×3개의 요소를 갖는다. 즉 2×3×1 바이트의 메모리가 확보
  된다. d는 4×5×8개의 배열요소를 갖고, 정수형이 2바이트인 경우 4×5×8×2
  바이트의 메모리가 확보된다. 배열 a[2][3]은 다음과 같이 메모리에 배치된다.


  │            │
  ├──────┤
  │  a[0][0]   │
  ├──────┤
  │  a[0][1]   │
  ├──────┤
  │  a[0][2]   │
  ├──────┤
  │  a[1][0]   │
  ├──────┤
  │  a[1][1]   │
  ├──────┤
  │  a[1][2]   │
  ├──────┤
  │            │

  배열 a의 i번째 j번째 요소를 참조하기 위해서는 아래와 같이 여러가지 방식이
  있다.

  a[i][j]
  *(a[i] + j)
  (*(a + i)[j]
  *((*(a + i) + j)

 [다차원 배열 사용 예]

 #include <stdio.h>
 int day_of_year(int y, int m, int d);
 void month_day(int y, int yd, int *pm, int *pd);
 static char daytab[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
 };                                                               ←①           

 main()
 {
   int year, month, day, yearday;

   scanf("%d %d %d", &year, &month, &day);
   printf("%d: %d. %d. ==> %d\\n", year, month,
          day, day_of_year(year, month, day));
   scanf("%d %d", &year, &yearday);
   month_day(year, yearday, &month, &day);
   printf("%d: %d ==> %d. %d.\\n",
          year, yearday, month, month, day);
 }

 
 int day_of_year(int year, int month, int day)
 {
   int i, leap;

   leap = ((year % 4 == 0) && (year % 100 != 0)) ||               ←②
          (year % 400 == 0);
   for(i = 1; i < month; i++)
      day += daytab[leap][i];
   return day;
 }

 
 void month_day(int year, int yearday,                            ←③
                int *pmonth, int *pday)           
 {
   int i, leap;

   leap = year % 4 == 0 && year % 100 != 0 ||                     ←④
          year % 400 == 0;
   for(i = 1; yearday > daytab[leap][i]; i++)
      yearday -= daytab[leap][i];
   *pmonth = i;
   *pday = yearday;
 }

 【실행 결과】 1999 3 5 엔터
               1999: 3. 5. ==> 64
               1999 64 엔터
               1999: 64 ==> 3. 5.
 【설     명】 ·위 프로그램은 년도와 월, 일을 입력으로 받아 해당년도의 몇
                 번째 날인가를 계산하고, 그 역으로 해당년도의 날수를 입력
                 으로 받아 월, 일을 계산하는 프로그램이다.
               ·①은 평년과 윤년의 각 월의 날수를 2차원 배열로 초기화 하
                 였다. daytab[0][1] ∼ daytab[0][12] 는 평년의 12개월 날수
                 가 입력되고, daytab[1][1] ∼ daytab[1][12] 는 윤년의 12개
                 월 날수가 초기화 된다.
               ·②는 윤년인 조건을 판정하여 4로 나누어 떨어지면서 100으로
                 나누어 떨어지지 않거나 400으로 나누어 떨어지면 윤년이 되
                 어 leap이 1이 되고 평년인 경우 0이 된다. 우선순위는 관계
                 연산자가 논리연산자 보다 높으므로 ④와 같이 괄호를 생략
                 할 수 없다.
               ·③은 반환되는 값이 두 개 이므로 return문으로 반환할 수 없
                 으므로 두개의 포인터 인수에 반환된다.

  [다차원 배열 인수 전달 예]

  #include 
  void disp(int dt[][5]);
  main()
  {
    int a[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };
    disp(a);
  }

  void disp(int dt[][5])    ←①
  {
    int i, j;
    for(i = 0; i < 3; i++) {
       for(j = 0; j < 5; j++)
          printf("%2d", dt[i][j]);
       putchar('\\n');
    }
  }

  【실행 결과】  1   2   3   4   5
                 6   7   8   9  10
                11  12  13  14  15
  【설     명】 ·다차원 배열의 인수 전달 시 ①과 같이 배열의 첫번째 요소
                  의 크기를 제외한 모든 배열 요소의 크기를 지정해야만 컴
                  파일러가 정확한 메모리 내의 배열요소의 배치를 결정할 수
                  있다.

 3. 포인터 연산

 ▣ 포인터의 번지 계산

  포인터에 1을 증가시키는 동작은 번지값이 1만큼 증가하는 것을 의미하는 것
  이 아니고 포인터가 가리키는 자료형의 바이트 수 만큼 증가된다. 즉, 「++p」
  인 경우에는 「sizeof(자료형)」의 바이트 수 만큼 증가됨을 의미한다. char,
  int, long int가 각각 1, 2, 4 바이트의 크기로 표현되는 경우 「++p」은 의
  미는 다음과 같다.

  ┌──────┬────┬──────┐
  │   선  언   │ 처  리 │   의  미   │
  ├──────┼────┼──────┤
  │  char *p;  │  ++p;  │1바이트 증가│
  ├──────┼────┼──────┤
  │  int *p;   │  ++p;  │2바이트 증가│
  ├──────┼────┼──────┤
  │long int *p;│  ++p;  │4바이트 증가│
  └──────┴────┴──────┘

  다음 예의 경우에는 p는 3개의 자료형 요소 크기, 즉 6바이트 증가된다.

  int *p;
  p = p + 3;

 ▣ 포인터의 연산

  포인터의 연산에는 다음과 같은 4개의 연산자 만을 사용할 수 있다.

  ┌────┬────────┐
  │ 연산자 │       예       │
  ├────┼────────┤
  │   +    │ p = p + 2;     │
  ├────┼────────┤
  │   -    │ p = p - 1;     │
  ├────┼────────┤
  │   ++   │ ++p; 또는 P++; │
  ├────┼────────┤
  │   --   │ --p; 또는 p--; │
  └────┴────────┘

  포인터의 연산은 정수형의 덧셈, 뺄셈만이 허용되고 곱셈, 나눗셈은 할 수 없
  다. 또한 포인터간의 덧셈, 뺄셈도 허용되지 않는다. p, p1, p2가 포인터라면,
  p = p1 + p2; 의 연산은 의미가 없는 연산이 된다. 다만 두 개의 포인터가 같
  은 배열의 임의의 요소를 가리킬 때 뺄셈은 유효하다. 포인터의 번지내용이 미
  정의를 의미하고자 하는 경우에는 NULL의 대입은 허용된다.
  void * 형의 포인터는 특수한 포인터로서 취급하여 ++, --의 연산을 할 수 없
  다. 즉 void * 형은 가리키는 자료요소의 크기가 없기 때문에 계산이 불가능
  하고 의미가 없게 된다.

  [포인터 연산 예]

  char s[10], *cp1, *cp2;
  char ss[10], *ssp;
  int i, *ip;
  float f;
  cp1 = &s[3];
  cp2 = &s[5];
  ssp = ss;
  ip = &i;

  (1) 정수의 뺄셈, 덧셈
      ip = ip + 2;
      ip = ip - 3;
      ++ip;
      --ip;
      ip = ip + i;
      ip = ip + f;    …오류발생, f는 정수형이 아니다
  (2) NULL(=0)의 대입
      p = NULL;
  (3) 2개의 포인터가 동일 배열의 임의의 요소를 가리키는 경우의 뺄셈
      i = cp2 - cp1;    …i는 2가 된다
      i = cp2 - ssp;    …다른 배열의 포인터 간의 뺄셈으로서 오류는 아니나
                          의미가 없다

 ▣ 포인터 연산의 우선순위

  포인터 연산자 *, & 연산자는 단항연산자인 ++, --와 같은 우선순위를 가지므
  로 다른 어떤 연산자 보다도 우선순위가 높다. 그러나 단항연산자인 ++, --등
  과 함께 사용하는 우선순위에 주의해야 한다. 동일한 우선순위의 단항연산자
  오 포인터 연산자가 사용되는 경우에는 우결합 규칙이 적용되어 우에서 좌로
  계산이 이루어진다.

 4. 포인터와 문자열

  C 언어에서 문자열의 표현을 char형의 배열을 사용하여 표현한다. 따라서 포
  인터와 배열과의 관계를 이해하면 포인터로서 문자열의 조작을 이해할 수 있
  다. 포인터로서 문자열을 처리하면 문자열의 시작번지를 조작하여 문자열이
  처리되고 배열로서 처리하면 배열의 각 요소에 저장된 문자자료로서 처리된다.

 ▣ 문자열의 초기화 및 설정

  char s[] = "abc";
  char *p = "xyz";

  위와 같이 선언하고 초기화하면 배열 s에 "abc"가 저장된다. 한편 포인터 p에
  는 임의의 메모리에 저장된 문자열 "xyz"의 시작번지가 p에 대입된다.

           s[0]  s[1]  s[2]  s[3] 
         ┌──┬──┬──┬──┐
  배열 s │'a' │'b' │'c' │'\\0'│
         └──┴──┴──┴──┘

         ┌──┐        ┌──┬──┬──┬──┐
  포인터p│ · ┼───→│'x' │'y' │'z' │'\\0'│
         └──┘        └──┴──┴──┴──┘
                           ↑
                           └ 컴파일러에 의해 임의의 메모리 위치에 저장

  문자열을 배열에 저장하기 위해서는 표준함수인 strcpy() 함수를 이용하여 저
  장한다.

  strcpy(s, "def");
  p = "A9BZ";

  초기화 시와 동일하게 배열 s에 문자열 "def"가 저장되고, 포인터 p에는 임의
  의 메모리에 저장된 문자열 "A9BZ"의 시작번지가 대입된다.

          s[0]  s[1]  s[2]  s[3] 
         ┌──┬──┬──┬──┐
  배열 s │'d' │'e' │'f' │'\\0'│
         └──┴──┴──┴──┘

   ┌──┐        ┌──┬──┬──┬──┬──┐
  p│ · ┼───→│'A' │'9' │'B' │'z' │'\\0'│
   └──┘        └──┴──┴──┴──┴──┘

 ▣ 문자열의 복사

  char s1[10], s2[] = "ABC";
  char *p1, *p2 = "DEFG";
  strcpy(s1, s2);
  p1 = p2;

  위의 예에서 배열 s2에 저장된 문자열 "ABC"가 배열 s1에 복사된다. 한편 포인
  터 p1에는 문자열 "DEFG"의 시작번지가 대입된다. 그 결과 p1과 p2는 같은 문
  자열을 가리킨다.

    s1[0] s1[1] s1[2] s1[3] 
  ┌──┬──┬──┬──┐
  │'A' │'B' │'C' │'\\0'│
  └──┴──┴──┴──┘
     ↑    ↑    ↑    ↑    개개의 문자가 복사된다
  ┌──┬──┬──┬──┐
  │'A' │'B' │'C' │'\\0'│
  └──┴──┴──┴──┘
    s2[0] s2[1] s2[2] s2[3]

          ┌──┐
        p1│ · ┼─┐
          └──┘  │
  번지가     ↑     │
  대입된다┌──┐  └─→┌──┬──┬──┬──┬──┐
        p2│ · ┼───→│'A' │'9' │'B' │'z' │'\\0'│
          └──┘        └──┴──┴──┴──┴──┘

  위와 같이 문자열을 포인터로 표현하면 문자열의 실체인 개개의 문자가 이동
  하는 것이 아니고 그 시작번지 만이 이동되므로 마치 문자열을 복사하는 것
  과 같은 효과를 얻을 수 있다. 즉, 포인터를 이용하면, 문자열의 실체는 이
  동하지 않고 그 시작번지 만을 가지고 조작이 가능하므로 문자열 처리 시간
  을 단축시킬 수 있다.

  [문자열 처리 예]

  #include <stdio.h>

  int slen1(char s[]);
  int slen2(char *s);

  main()
  {
    printf("%d\\n", slen1("COMPUTER"));
    printf("%d\\n", slen2("COMPUTER"));
  }

  int slen1(char s[])
  {
    int i;
    for(i = 0; s[i] != '\\0'; ++i)
    ;
    return i;
  }

  int slen2(char *s)
  {
    char *p = s;
    while(*p != '\\0')
      p++;
    return p-s;
  }

 【실행 결과】 8
               8
 【설     명】·문자열의 갯수를 세는 프로그램으로서 표준함수 strlen()과 같
                은 기능을 한다.

 5. 포인터의 배열

 ▣ 포인터의 배열

  포인터는 배열로서 선언될 수 있는데 주로 문자열의 표현에 이용된다. 포인터
  배열은, char *str[5] 과 같이 선언한다. 즉, char형을 가리키는 배열요소 5
  개가 아래와 같이 표현된다.

        │        │
        ├────┤
  str[0]│    ──┼──→ ┐
        ├────┤       │
  str[1]│    ──┼──→ │
        ├────┤       │
  str[2]│    ──┼──→ │→char형의 자료를 가리킨다.
        ├────┤       │
  str[3]│    ──┼──→ │
        ├────┤       │
  str[4]│    ──┼──→ ┘
        ├────┤
        │        │

  char형의 포인터의 배열은 2차원 배열과 그 의미가 유사하다. 예를들어 다음
  과 같은 2차원 배열은

  char nv[3][10] = { "January", "February", "March" };

  아래의 포인터 배열과 그 의미가 유사하다.

  char *np[3] = { "January", "February", "March" };

  위의 두 방법은 메모리 확보 방법에 차이가 있다. 2차원 배열은 각각의 배열
  요소에 개개의 문자가 저장된다. 반면에 포인터 배열에는 임의의 메모리에 확
  보된 3개의 문자열의 시작번지가 각 포인터 배열요소에 저장된다.
  메모리에 저장되는 방법은 아래 그림과 같다.

          [0]   [1]   [2]   [3]   [4]   [5]   [6]   [7]   [8]   [9]
        ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  nv[0] │'J' │'a' │'n' │'u' │'a' │'r' │'y' │'\\0'│ ?  │ ?  │
        └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  nv[1] │'F' │'e' │'b' │'r' │'u' │'a' │'r' │'y' │'\\0'│ ?  │
        └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  nv[2] │'M' │'a' │'r' │'c' │'h' │'\\0'│ ?  │ ?  │ ?  │ ?  │
        └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

        │    │
        ├──┤     ┌──┬──┬──┬──┬──┬──┬──┬──┐
  np[0] │ · ┼─→ │'J' │'a' │'n' │'u' │'a' │'r' │'y' │'\\0'│
        ├──┤     └──┴──┴──┴──┴──┴──┴──┴──┘
  np[1] │ · ┼┐   ┌──┬──┬──┬──┬──┬──┬──┬──┬──┐
        ├──┤└→ │'F' │'e' │'b' │'r' │'u' │'a' │'r' │'y' │'\\0'│
  np[2] │ · ┼┐   └──┴──┴──┴──┴──┴──┴──┴──┴──┘
        ├──┤│   ┌──┬──┬──┬──┬──┬──┐
        │    │└→ │'M' │'a' │'r' │'c' │'h' │'\\0'│
                     └──┴──┴──┴──┴──┴──┘

  위의 그림에서와 같이 2차원 배열의 경우 설정된 문자열의 길이가 배열의 크기
  보다 작은 경우에는 메모리가 낭비된다. 반면 포인터 배열의 경우에는 문자열
  길이 만큼만 메모리를 사용한다. 그 대신 각 문자열의 시작번지를 저장하는 포
  인터의 영역이 필요하게 된다.

  [포인터 배열 사용 예]

  #include 
  char *month_name(int n);
  main()
  {
    int m;

    while(scanf("%d", &m) != EOF)
      printf("%d ==> %s\\n", m, month_name(m));
  }

  
  char *month_name(int n)
  {
    static char *name[] = {
           "Illegal month",
           "January", "February", "March",
           "April", "May", "June",
           "July", "August", "September",
           "October", "November", December"
    };
    return(n < 1 || n > 12) ? name[0] : name[n];
  }

 【실행 결과】 10 엔터
               10 ==> October
               7  엔터
               7  ==> July
               13 엔터
               Illegal month
               Ctrl + Z 엔터
 【설     명】 ·조건식 ? 식1 : 식2; → 조건식이 참이면 식1의 결과를 조건
                                        식이 거짓이면 식2의 결과를 구한다.

 ▣ 포인터의 포인터

  char **str;

  이것은 str이 char형의 자료값을 가리키는 포인터를 가리키는 포인터로서 선
  언됨을 의미한다. 포인터의 포인터는 포인터 배열과 유사하게 사용된다.

  char *a[10];

  은 포인터 용으로서 10개의 장소가 확보되지만,

  char **a;

  는 포인터용으로 다만 1개의 장소만 확보된다. 포인터의 포인터 선언에서는
  초기화에 주의해야 한다. 보통은 다른 방법으로 메모리를 확보한 후에 포인
  터의 포인터에 전달한다. 다음 프로그램은 포인터의 포인터 구조를 보여주
  는 예이다.

  [포인터의 포인터 사용 예]

  #include 
  main()
  {
    char *a[2];
    char **p;

    a[0] = "ABC";
    a[1] = "DEF";

    printf("%s\\n", a[0]);
    printf("%s\\n", a[1]);
    p = a;
    printf("%s\\n", *p);
    printf("%s\\n", *(p+1));
    printf("%c, %c, %c\\n", *a[0], *(a[0] + 1), *(a[0] + 2));
    printf("%c, %c, %c\\n", **p, *(*p + 1), *(*p + 2));
  }

 【실행 결과】 ABC
               DEF
               ABC
               DEF
               A, B, C
               A, B, C

 ▣ 명령행 인수

  C 언어에서는 주프로그램인 main() 함수에서도 인수를 전달할 수 있다. 이때
  전달되는 인수는 운영체제와의 통신을 위해 기술되므로 명령행 인수라 한다.
  명령행 인수의 사용형식은 다음과 같다.

  명령행 인수 사용형식 : main(int argc, char *argv[])
                         {
                            :
                         }
                       ·argc : 인수의 갯수(argument count)가 전달된다.
                       ·argv : 인수 벡터(argument vector)로서 각 인수
                                 의 문자열을 가리키는 포인터 배열이다.

  예를 들어 아래와 같은 운영체제의 명령줄을 작성하면,

     $     command  arg_1 arg_2 ...... arg_n
     ─    ───   ────────────
     ↑      ↑               ↑
  운영체제 명령어            인수
  프롬프트

  argc는 n+1이 되고 argv의 포인터 배열은 아래와 같이 각 인수를 가리킨다.

         │        │
         ├────┤
  argv[0]│    ──┼──→ "command"
         ├────┤       
  argv[1]│    ──┼──→ "arg_1"
         ├────┤       
  argv[2]│    ──┼──→ "arg_2"
         ├────┤       
         │   :   │          :
         ├────┤       
  argv[n]│    ──┼──→ "arg_n"
         ├────┤
         │        │

  [명령행 인수 사용 예 1]

  
  #include 
  main(int argc, char *argv[]}
  {
    int i;

    printf("argc ==> %d\\n", argc);
    for(i=0; i %s\\n", i, argv[i]);
  }

 【실행 결과】 A> argtst AAA BB CCCC
               argc ==> 4
               argv[0] ==> argtst
               argv[1] ==> AAA
               argv[2] ==> BB
               argv[3] ==> CCCC

  [명령행 인수 사용 예 2]

  
  #include 
  main(int argc, char **argv)
  {
    int i, s, e;

    if(argv < 3) {                ←①
      printf("input error\\n");
      exit(1);                    ←②
    }

    s = atoi(*++argv);
    e = atoi(*++argv);
    for(i=s; i<=e; i++)
       printf("%4d %4x %4c\\n", i, i, i);
  }

 【실행 결과】 A> ascii 33 36 엔터
                  33   21   !
                  34   22   "
                  35   23   #
                  36   24   $
 【설     명】 ·위 프로그램은 명령행에서 ACSII 10진수의 시작과 끝을 의미
                 하는 2개의 값을 전달받아 ASCII code의 10진수, 16진수, 문
                 자를 출력하는 프로그램이다.
               ·①인수가 3개 이하인 경우, 즉 ASCII의 시작, 끝 값 중 하나
                 가 누락된 경우 오류메세지를 출력한 후 실행을 종료한다.
               ·②의 exit() 함수는 프로그램의 실행을 강제 종료시킨다. 보
                 통, 인수로서 0을 기술하면 정상 종료, 1이면 비정상 종료를
                 의미한다.
               ·atoi(s)는 숫자 문자열 s를 인수로 받아 int형으로 변환하여
                 반환하는 함수이다.

 6. 함수의 포인터

  포인터는 보통 변수의 번지를 저장하여 가리키지만 C 언어에서는 함수의 시작
  번지 즉, 함수의 실행코드의 시작번지를 가리킬 수 있다. 이 경우에느 포인터
  사용하여 함수를 호출하여 실행시킬 수 있다.

 ▣ 함수의 포인터 선언

  함수의 포인터 선언은 보통 포인터 선언과 비슷하다. 예를 들면,

  int a;
  int *a;   ←a는 int형의 포인터이다

  의 변수의 포인터 선언과 비교하면, 함수의 포인터 선언은

  int fptr();      ←int형을 반환하는 함수 fptr
  int *fptr();     ←int형의 포인터를 반환하는 함수 fptr
  int (*fptr)();   ←fptr은 int형을 반환하는 함수의 실행코드 시작을 가리키
                     는 포인터이다

  이 된다.

  함수의 포인터 선언 : 반환자료형 (*포인터 명)();

  위의 예에서 int *(fptr());로 선언하지 않도록 주의해야 한다. 이의 의미는
  int *fptr()과 동일하게 해석되어 정수형 포인터를 반환하는 함수로 해석된다

  [함수의 포인터 사용 예]

  #include 
  void sum(int a, int b);
  void sub(int a, int b);

  main()
  {
    void (*fun)(int a, int b);
    int a = 10, b = 3;

    fun = sum;
    fun(a, b);
    fun = sub;
    fun(a, b);
  }

  void sum(int a, int b)
  {
    printf("sum : %d\\n", a+b);
  }

  void sub(int a, int b)
  {
    printf("sub : %d\\n", a-b);
  }

 【실행 결과】 sum : 13
               sub : 7

 ▣ 함수의 포인터 배열

  함수의 포인터는 배열로서도 선언할 수 있는 데, 이때 각 배열요소는 지정된
  함수의 시작번지를 가리킨다. 예를 들면, int (*calcu[4])(int a, int b)라
  고 선언하면 calcu는 함수의 포인터 배열로서 4개의 각 함수의 시작 번지를
  지정할 수 있다.

  [함수의 포인터 배열 사용 예]

  #include 
  int sum(int a, int b);
  int sub(int a, int b);
  int mul(int a, int b);
  int div(int a, int b);

  main()
  {
    int i;
    int(*calcu[4])(int a, int b);

    calcu[0] = sum;
    calcu[1] = sub;
    calcu[2] = mul;
    calcu[3] = div;
    for(i = 0;i <= 3;i++)
       printf("%d\\n", calcu[i](12,4));
  }

  int sum(int a, int b)
  {
    return a + b;
  }

  int sub(int a, int b)
  {
    return a - b;
  }

  int mul(int a, int b)
  {
    return a * b;
  }

  int div(int a, int b)
  {
    return a / b;
  }

 【실행 결과】 16
               8
               48
               3

  [함수 포인터의 인수 전달 예]

  #include <stdio.h>
  void calcu(void(*func)(int a, int b), int x, int y);
  void sum(int a, int b);
  void sub(int a, int b);

  main()
  {
    calcu(sum, 20, 7);
    calcu(sub, 20, 7);
  }

  void calcu(void(*func)(int a, int b), int x, int y)
  {
    func(x, y);
  }

  void sum(int a, int b)
  {
    printf("sum = %d\\n", a+b);
  }

  void sub(int a, int b)
  {
    printf("sub = %d\\n", a-b);
  }

 【실행 결과】 sum = 27
               sub = 13
 【설     명】 ·함수의 포인터는 인수로서도 전달할 수 있다. 실제로는 대개
                 함수의 포인터는 인수로서 전달하여 사용한다.

 포인터와 배열(Pointer and Arrays)에 대해서 다 알아 보았습니다.
 다음에는 구조체(Structures)에 대해서 알아봅시다.

 이 강좌는 저자(adonrs)의 허락없이 다른곳에 올릴 수 없음을 알려 드립니다.

 (올렸는데..음..)