C 포인터

 C�포인터.bmp

 

목차


  1. 변수와 메모리
  2. 변수의 주소
  3. 주소의 가,감산
  4. 주소와 메모리
  5. 포인터의 선언
  6. 다차원 포인터
  7. 다차원 포인터의 가,감산
  8. 배열의 선언
  9. 다차원 배열
  10. 다차원 주소의 의미
  11. 배열 요소에 접근하기
  12. 1차원 포인터와 배열
  13. void형 포인터
  14. 함수 포인터

 

변수는 데이터를 저장하기 위한 메모리 공간의 이름이다. 변수는 자료형(Data Type)으로 메모리 공간의 크기와 형태(정수, 실수)를 나타낸다. 메모리의 주소 하나는 1byte 크기의 메모리 공간을 갖는다. 예를 들어 크기가 4byte인 메모리의 주소는 총 4개의 주소를 사용한다.

다음은 char형 변수를 메모리에 할당하는 예제이다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 'A';

  5. }

C_Pointer1.bmp

  c 변수는 char형이므로 c는 1byte의 크기를 사용한다. 하나의 주소는 1byte의 저장 공간을 가지고 있으므로 위의 그림과 같이 c 변수는 1byte에 'A' 문자의 ASCII 코드인 65(0x41)를 저장한다. 또 그림처럼 주소는 1000번지 하나를 사용하며 메모리 주소 하나는 1byte의 크기를 갖는다.

 컴퓨터 메모리는 수자(2진수 형태)만을 저장할 수 있으므로 문자는 컴퓨터 메모리에 직접 저장할 수 없다. 그래서 문자를 컴퓨터 메모리에 저장하기 위해서는 미리 정의한 문자의 숫자(정수)를 저장한 다. 이와 같이 정의된 숫자를 문자 코드라 하고 ASCII코드, UNICODE 등이 있다. 이중에 C언어는 문자를 메모리에 저장할 때 ASCII 코드를 사용한다. ASCII 코드의 문자 'A'는 정수로 65, 'B'는 66등으로 정의되어 있다. 한마디로 C언어 문법에서는 65는 'A'로, 'A'는 65로 표현하는 것이 가능하다.

  다음은 int형 예제이다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    int n = 100;

  5. }

C_Pointer2.bmp

  n 변수는 int형 이므로 n은 4byte의 크기를 사용한다. 또 그림처럼 주소는 1000~1003까지 4개의 주소( 주소 하나는 1byte 저장 공간을 갖는다)를 사용한다.

  모든 변수는 메모리 공간에 할당되고 변수의 크기는 자료형으로 결정된다. 따라서 자료형을 보면 변수의 크기와 메모리 주소의 사용 개수를 알 수 있다. 또 변수에 '&' 연산자를 붙이면 변수의 시작 주소를 의미한다.

  아래는 실제 주소를 출력한 예제이다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 'A';

  5.    printf("%c\n", c);

  6.    printf("%d %x\n", &c, &c);

  7. }

  8. ----------------------------------

  9. 출력결과

  10. A

  11. 1245052   12ff7c

C_Pointer3.bmp

  1byte의 메모리를 갖는 c변수의 시작 주소는 10진수로 1245052, 16진수로 12ff7c를 사용하는 것을 볼 수 있다.

 

  변수의 주소는 연산자 '&'를 변수 이름 앞에 붙여 사용한다. 변수들의 주소는 자료형에 따라 주소의 의미가 다르다. 다음은 간단한 char형 주소의 의미를 알아보기 위해 char형 주소에 정수를 가산해 보도록 하겠다.

  1. 변수 c1의 주소가 16진수로 12ff7c라 하고 c2의 주소가 12ff78이라 하자.

  2. #include <stdio.h>

  3. void main()

  4. {

  5.    char c1 = 'A';

  6.    char c2 = 'B';

  7.    printf("%x\n", &c1+1);

  8.    printf("%x\n", &c2+1);

  9. }

  10. ---------------------------

  11. 출력결과

  12. 12ff7d

  13. 12ff79

  char형 주소에 정수를 가산하라는 명령은 char형 메모리 공간을 건너뛴 다음 메모리의 주소를 구하라는 명령이다. 아래 그림을 참고하자.

C_Pointer4.bmp

  c1의 주소가 12ff7c이므로 &c1+1은 1byte(char형 크기)를 건너뛴 12ff7d이고 c2의 주소가 12ff78이므로 &c2+1은 1byte(char형 크기)를 건너뛴 12ff79이다.

  char형 주소에 +1을 하면 주소 하나가 증가되고 int형 주소에 +1을 하면 주소 4개가 증가한다. 이유는 간단하다. char형 주소는 1byte에 데이터를 가지고 있는 시작 주소(대표 주소)이고 int형 주소는 4byte 데이터를 가지고 있는 시작 주소(대표 주소)이기 때문에 가지고 있는 데이터만큼 주소를 건너뛰게 된다. 이것은 포인터를 이해하는 데 있어서 매우 중요하다. '시작 주소(대표 주소)마다 그 의미가 다르다.'

  즉, char 형 변수의 시작 주소는 1byte char형 메모리의 대표 주소를 의미하고 int형 변수의 시작 주소는 4byte int형 메모리의 대표 주소를 의미한다.

  주소에 정수를 가산하거나 감산하면 결과는 아래와 같이 된다.

자료형의 주소 +,- 정수 * sizeof(자료형) = 자료형의 주소

 

 

  형변환은 암시적인 형변환(자동 형변환)과 명시적인 형변환(강제 형변환)이 있다. 암시적인 형변환은 컴파일러에 의해 자동으로 변환되고 명시적인 형변환은 '()'연산자를 사용하여 변환한다.

  char형 변수(1byte)를 int형 변수(4byte)에 대입하는 경우 암시적인 형변환(자동 형변환)이 수행된다. 작은 자료형을 큰 자료형으로 변환하는 경우 데이터 손실이 없으므로 컴파일러는 자동으로 형변환을 수행한다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 5;

  5.    int n, n2;

  6.    n = c;                // 암시적인 형변환   

  7.    n2 = (int) c;         // 명시적인 형변환

  8.    printf("%d\n", n);

  9.    printf("%d\n", n2);

  10. }

  11. ------------------------------

  12. 출력결과

  13. 5

  14. 5

  중요한 사실은 모든 연산은 동일한 형(Type)에서만 가능하다는 것이다.

  주소도 변수의 자료형과 마찬가지로 명시적인 형변환이 가능하다.

다음은 char형 주소를 int형의 주소로 명시적인 형변환을 하여 정수를 가산한 예제이다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4.    char c = 'A';
  5.    printf("%x %x %x\n", &c, (char*)&c, (int*)&c);
  6.    printf("%x %x %x\n", &c + 1, (char*)&c + 1, (int*)&c + 1);
  7.    printf("%x %x %x\n", &c + 2, (char*)&c + 2, (int*)&c + 2);
  8.    printf("%x %x %x\n", &c + 3, (char*)&c + 3, (int*)&c + 3);
  9. }
  10. ---------------------------------------------------------------------
  11. 출력결과
  12. 12ff7c   12ff7c   12ff7c
  13. 12ff7d   12ff7d   12ff80
  14. 12ff7e   12ff7e   12ff84
  15. 12ff7f   12ff7f   12ff88

C_Pointer5.bmp

  '&c'에 '(char*)'와 '(int*)'를 붙여 각각 주소 형변환을 하고 있다. 이때 '(char*)'는 '&c'가 char형 주소이므로 두 형태는 같다. (&c == (char*)&c) 하지만 '(int*)'를 붙여 '&c'를 int형 주소로 강제 변환하고 이곳에 정수를 가산하면 주소가 4byte씩 증가하는 것을 볼 수 있다. 

 

  이번에는 어떤 자료형의 주소가 있을 때 그 자료형의 값이나 메모리를 어떻게 접근하는지 알아보도록 하겠다. 간단하게 자료형 주소에 '*' 연산자를 붙여 주면 된다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 'A';

  5.    printf("%x\n", &c);

  6.    printf("%c %c\n", c, *&c);

  7. }

  8. -----------------------------------

  9. 출력결과

  10. 12ff7c

  11. A  A

 '&'연산자와 '*'연산자는 상반된 개념을 가지고 있다. 또 '&'연산자는 변수(메모리 이름) 앞에서만 사용하고 '*'연산자는 주소 앞에서만 사용한다. 예를 들어 '*N'과 같이 사용하면 N은 주소이고 '&N'과 같이 사용하면 N은 변수(메모리 이름)여야만 한다. 만약 N이 주소라면 '*N'과 같이 하면 메모리 이름이 되고 '&*N'과 같이 하면 주소가 되고 '*&*N'과 같이 하면 메모리가 된다.

 프로세서는 메모리에 데이터를 저장하기 위해 몇 가지 방법을 사용한다. 프로세서의 데이터 저장 방식 중 가장 많이 사용하는 little_endian과 big_endian에 대해 간략하게 언급하면 little_endian은 메모리에 데이터를 저장할 때 낮은 주소에서 높은 주소로 하위 byte부터 상위 byte까지 저장하는 방식이고 big_endian은 낮은 주소에서 높은 주소로 상위 byte부터 하위 byte까지 저장하는 방식이다.

아래는 주소 1000번지부터 주소 1003번지까지 정수 0x12345678을 저장하는 예이다.

little_endian은 1000번지에 0x78, 1001번지에 0x56, 1002번지에 0x34, 1003번지에 0x12를 각각 저장한다.

big_endian은 1000번지에 0x12, 1001번지에 0x34, 1002번지에 0x56, 1003번지에 0x78를 각각 저장한다.

  char형 주소를 int형 주소로 변환하여 값에 접근한 예

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 10;

  5.    printf("%d\n", c);

  6.    printf("%d\n", *&c);

  7.    printf("%d\n", *(int*)&c);

  8. }

  9. ----------------------------------

  10. 출력결과

  11. 10

  12. 10

  13. -858993654(쓰레기값)

C_Pointer6.bmp

이 예제는 char형 주소를 int형 주소로 강제 변환한 후 값에 접근한다. 첫 번째 문장과 두 번째 문장은 10이지만 세 번째 문장은 '알 수 없음'이다. 위의 그림을 보면 이해할 수 있을 것이다. 12ff7c 주소에 10이 저장되고 나머지 12ff7d, 12ff7e, 12ff7f 주소는 데이터를 저장한 것이 아니기 때문에 값을 알 수 없고 12ff7c부터 12ff7f까지의 내용에 접근하는 (*(int*)&c)는 쓰렉기 값이 된다.

 

 

포인터란 주소를 저장하기 위한 메모리 공간이다. 즉, 주소를 저장하는 변수를 포인터라 한다. 포인터 변수는 변수 이름 앞에 '*'를 붙여 선언한다.

  1. char c = 'A';

  2. char *cp = &c;   // char형 포인터 변수 cp 선언

  3. int n = 20;

  4. int *np = &n;    // int형 포인터 변수 np 선언

C_Pointer7.bmp

  예에서와 같이 선언하면 위의 그림(메모리 내용)과 같이 char형 포인터 변수 cp와 int형 포인터 변수 np는 &c와 &n의 주소 12ff7c(변수들의 시작 주소)를 저장한다. 포인터 변수가 변수 c와 n의 주소를 가지고 있다는 표현으로 보통 위의 그림(구조 표현)과 같이 도식화하기도 한다.

  변수 c는 정수 65(1byte)를 저장하고 변수 n은 정수 20(4byte)를 저장하고 변수 cp는 주소 12ff7c(char형 주소)를 저장하고 변수 np는 주소 12ff7c(int형 주소)를 저장한다.

 

 

다차원 포인터는 포인터 변수의 주소를 저장하는 포인터이다.

다음은 char형 1차원 포인터와 2차원 포인터 변수를 사용한 예제이다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char c = 'A';

  5.    char *cp;

  6.    char **cpp;

  7.       cp = &c;
  8.       cpp = &cp;
  9.       printf("%c %c %c\n", c, *cp, **cpp);
  10.       printf("%x %x %x\n", &c, cp, *cpp);
  11.    }
  12. -------------------------------------------------------
  13. 출력결과
  14. A  A  A
  15. 12ff7c  12ff7c  12ff7c

 c_Pointer.bmp

  2차원 포인터 변수 cpp는 2차원 char형 주소 &cp를 가지고 있고 1차원 포인터 변수 cp는 1차원 char형 주소 &c를 가지고 있다. 또 cpp도 주소를 저장하는 포인터 변수이므로 4byte 크기이다. cp는 1차원 char형 주소이므로 *cp는 1byte 변수 c와 같은 메모리이고 cpp는 2차원 char형 주소이므로 **cpp는 1byte 변수 c와 같은 메모리이다. 이처럼 1차원 주소에 '*' 연산자를 붙여 값에 접근하고 2차원 주소에 '**" 연산자를 붙여 값에 접근한다.

 

  모든 자료형의 주소는 의미를 가지며 가,감산이 가능하다. 주소가 가지는 내용물(데이터)의 크기에 따라 주소의 결과가 달라진다. char형 주소에 +1하면 char형 크기만큼 건너뛴 주소 1이 증가하고 int형 주소에 +1하면 int형 크기만큼 건너뛴 주소 4가 증가한다.

 다음은 int형 변수와 포인터 변수의 가,감산 예제이다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    int n = 20;

  5.    int *np;

  6.    int **npp;

  7.    np = &n;

  8.    npp = &np;

  9.    printf("%x %x %x\n", &n, &np, &npp);

  10.    printf("%x %x %x\n", &n+1, &np+1, &npp+1);

  11. }

  12. ----------------------------------------------------

  13. 출력결과

  14. 12ff7c  12ff78  12ff74

  15. 12ff80  12ff7c  12ff78

c_Pointer2(1).bmp 

  n은 int형 변수이고 &n은 1차원 int형 주소이고 &np는 2차원 int형 주소이고 &npp는 3차원 int형 주소이다. &n(12ff7c)에 +1을 한 것은 int형(4byte)을 건너뛰겠다는 의미로 4가 증가(12ff80)하고 &np(12ff78)에 +1을 한 것은 int형 주소(4byte)만큼 건너뛰겠다는 의미로 4가 증가(12ff7c)한다. 또 &npp(12ff74)에 +1을 한 것은 int형 2차원 주소(4byte)만큼 건너뛰겠다는 의미로 4가 증가(12ff78)한다.

 

 포인터는 주소를 저장하기 위한 메모리 공간(변수)이다.

 포인터의 크기는 모두 4byte 이다.

 포인터 변수로 값에 접근할 때(메모리 공간)는 * 연산자를 사용한다.

 2차원 포인터 변수는 1차원 포인터 변수의 주소를 저장하기 위한 메모리 공간(변수)이다.

 n차원 포인터 변수는 n-1차원 포인터 변수의 주소를 저장하기 위한 메모리 공간(변수)이다.

 n차원 포인터 변수는 * 연산자를 n개 붙여 값(메모리 공간)을 접근한다.

 

 

 

배열이란 같은 자료형(데이터)의 연속적인 메모리 공간이다. 배열은 '[]' 연산자를 사용하여 선언한다.

  1. 예>
  2. char carr[5];
  3. int iarr[5];

 c_Pointer3(1).bmp

  이때 char carr[5]의 시작 주소가 12ff78이라면 1byte 크기를 연속적으로 5개 가지므로 12ff7c까지 5byte 크기를 갖는다. 각 메모리의 이름은 carr[0] ~ carr[4]처럼 사용된다. 배열 메모리(carr[5])의 시작 주소(대표주소)는 12ff78이고 배열의 이름은 carr이다.

배열의 이름은 연속적인 배열 메모리의 시작 주소이다. char carr[5]와 같은 배열의 시작 주소는 배열의 이름 carr이 된다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4.    char carr[5] = {1,2,3,4,5};
  5.    printf("%x %x\n", carr, &carr[0]);
  6. }
  7. ---------------------------------------------
  8. 출력결과
  9. 12ff78  12ff78

carr은 배열의 시작주소이고 5개의 배열 요소 중 첫 번째 변수 carr[0]에 주소 연산자 '&'를 붙이면 배열의 시작 주소와 같다는 것을 알 수 있다. carr[0] 메모리가 배열 메모리 시작이기 때문이다.

 

 '[]' 연산자는 자료형 선언 시에 사용하면 배열을 만들기 위한 연산자(char carr[5];)로 사용되며 배열 선언 뒤에 사용하면 배열 시작 주소+index가 가리키는 메모리(값)를 의미하는 연산자(carr[0])로 사용된다.

 

char carr[5]에서 carr은 배열의 시작 주소를 의미하며 1차원 char형 주소로 사용된다. 그러므로 carr에 '*'를 붙인 *carr은 char형 1byte 메모리 carr[0]를 나타낸다.

  1. #include <stddio.h>
  2. void main()
  3. {
  4.    char carr[5] = {'A', 'B', 'C', 'D', 'E' };
  5.    printf("%c %c %c %c %c\n",
  6.           *carr, *(carr+1), *(carr+2), *(carr+3), *(carr+4) );
  7. }
  8. ----------------------------------------------------------------
  9. 출력결과
  10. A B C D E

c_Pointer4(1).bmp 

carr[0]과 *carr, carr[1]과 *(carr+1)는 동일한 코드라는 것을 알 수 있다.

 배열 이름이 N( 예. int N[10] )이라면 N은 배열의 시작 주소이며 배열 요소(메모리)에 접근할 때 'N[index]'와 같이 접근할 수 있다. 또 'N[index]'는 '*(N+index)'와 같은 의미이다.

 

다차원 배열은 메모리 공간을 다차원처럼(1차원적인 메모리를 다차원적인 메모리처럼) 접근할 수 있는 기능을 제공한다.

다음은 같은 크기의 메모리를 1차원 배열과 2차원 배열로 사용한 예제이다. 다차원 배열은 '[]' 연산자를 여러 개 붙여 사용한다. char형 변수 6개를 연속적으로 선언한다.

  1. #include <stdio.h>

  2. void main()

  3. {

  4.    char carr1[6] = {'A', 'B', 'C', 'D', 'E', 'F'};

  5.    printf("%c %c %c %c %c %c\n",

               carr1[0], carr1[1], carr1[2], carr1[3], carr1[4], carr1[5]);

  6. }

  7. ---------------------------------------------------------------------------

  8. #include <stdio.h>

  9. void main()

  10. {

  11.    char carr2[2][3] = {'A', 'B', 'C', 'D', 'E', 'F'};

  12.    printf("%c %c %c %c %c %c\n",

  13.            carr2[0][0], carr2[0][1], carr2[0][2],

  14.            carr2[1][0], carr2[1][1], carr2[1][2]);

  15. }

  16. ------------------------------------------------------------

  17. 출력결과

  18. A B C D E F

크기변환_c_pointer(2).bmp 

  carr1은 1차원 배열의 시작 주소라 하고 carr2는 2차원 배열의 시작 주소라 한다. carr1 배열의 첫 번째 요소는 carr1[0]과 같이 접근하지만 carr2 배열의 첫 번째 요소는 carr2[0][0]와 같이 접근한다. 두 배열의 메모리와 크기는 같지만 배열 요소를 접근하는 방법이 조금 다르다는 것을 알 수 있다.

  다차원  배열의 이름 carr2는 2차원 char형 배열의 대표 주소, carr2[0]는 0행의 대표 주소, carr2[1]는 1행의 대표 주소를 의미한다.

 

     모든 주소는 가,감산이 가능하며 주소의 의미에 따라 결과가 다르다는 것을 공부했었다. 이제 예제를 통해 2차원 배열 주소의 의미를 살펴보도록 하겠다. 배열 시작 주소를 12ff7c라 가정한다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4.    char carr2[2][2] = {'A', 'B', 'C', 'D' };
  5.    printf("%x %x %x %x\n", &carr2[0][0], carr2, carr2[0], carr2[1]);
  6.    printf("%x %x %x %x\n", &carr[0][0]+1, carr2+1, carr2[0]+1, carr2[1]+1);
  7. }
  8. ------------------------------------------------------------------------------
  9. 출력결과
  10. 12ff7c 12ff7c 12ff7c 12ff7e
  11. 12ff7d 12ff7e 12ff7d 12ff7f

 c_pointer2(2).bmp

  &carr2[0][0]는 char형 1byte의 주소를 의미하며 여기에 +1을 한 것은 1byte의 주소를 가산하라는 의미이다. carr2는 char형 배열 2*2의 시작 주소를 의이하며 +1을 한 것은 열의 byte수인 2byte(char형 2개[2])의 주소를 건너뛰라는 의미이다. carr2[0]는 0행의 시작 주소를 의미하며 1차원 char형 주소의 의미로 +1을 한 것은 char형 1byte의 주소를 건너뛰라는 의미이다. 또 carr2는 char형 2차원 배열이므로 char형의 2차원의 의미를 갖고 carr2[0], carr2[1], &carr2[0][0]는 1차원의 의미를 갖는다.

  각 주소는 '*' 연산자를 붙여 값에 접근하거나 차원을 한 단계 낮추는데 사용한다. 예를 들어 1차원 주소에 '*' 연산자를 붙이면 값이 되지만 2차원 주소에 '*'연산자를 붙이면 1차원 주소가 되고, 3차원 주소에 '*'를 붙이면 2차원 주소가 된다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4.    char carr2[2][2] = {'A', 'B', 'C', 'D'};
  5.    printf("%x %x %x\n", carr2, *carr2, **carr2);
  6.    printf("%x %x %x\n", carr2, carr2[0], carr2[0][0]);
  7. }
  8. ----------------------------------------------------------
  9. 출력결과
  10. 12ff7c  12ff7c  41
  11. 12ff7c  12ff7c  41

c_pointer3(2).bmp 

  두 문장의 코드는 동일하다. (*carr2 == carr2[0]) carr2는 2차원 주소 12ff7c를, *carr2는 1차원 주소 12ff7c를, **carr2는 메모리 자체(문자'A')를 의미한다. *carr2는 1차원 주소를 의미하는데, carr2가 2차원 주소이므로 '*' 연산자를 붙여 1차원 주소가 된다. 또 **carr2는 char형 1byte를 의미하는데, *carr2가 1차원 char형 주소를 의미하기 때문이다.

 

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int iarr[6] = { 10, 20, 30, 40, 50, 60 };
  5. printf("%x\n", iarr);
  6. printf("%d %d %d %d %d %d\n",
  7. iarr[0], iarr[1], iarr[2], iarr[3], iarr[4], iarr[5]);
  8. printf("%d %d %d %d %d %d\n",
  9. *iarr, *(iarr+1), *(iarr+2), *(iarr+3), *(iarr+4), *(iarr+5));
  10. }
  11. ------------------------------------------------------------------------------
  12. 출력결과
  13. 12ff68
  14. 10 20 30 40 50 60
  15. 10 20 30 40 50 60

배열의 시작 주소 iarr은 int형 1차원 주소의 의미이므로 iarr이 12ff68이라면 iarr+1은 12ff6c이고, iarr+5는 12ff7c이다. iarr[0]은 *(iarr+0)이고 iarr[1]은 *(iarr+1)이고 iarr[5]은 *(iarr+5)이다.

아래 예제는 배열 이름(배열에 대표 주소)에 대한 이해를 돕는 예제이다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int iarr[6] = { 10, 20, 30, 40, 50, 60 };
  5. printf("%x\n", &iarr[2]);  // 12ff70
  6. printf("%d %d %d %d %d %d\n",
  7. iarr[0], iarr[1], iarr[2], iarr[3], iarr[4], iarr[5]);
  8. printf("%d %d %d %d %d %d\n", (&iarr[2])[-2], (&iarr[2])[-1],
  9. (&iarr[2])[0], (&iarr[2])[1], (&iarr[2])[2], (&iarr[2])[3]);
  10. }
  11. -------------------------------------------------------------------------
  12. 출력결과
  13. 12ff70
  14. 10 20 30 40 50 60
  15. 10 20 30 40 50 60

cp.bmp 

&iarr[2]는 int형 1차원 주소이고 위의 그림과 같이 &iarr[2]을 중심으로 +,- 연산을 하여 메모리에 접근할 수 있다는 것을 알 수 있다. (&iarr[2])[-2]는 *(&iarr[2]-2)로 주소 &iarr[2]의 주소 12ff70에서 2를 뺀 주소 12ff68에 '*' 연산자를 붙여 값 10을 의미한다. 이외에도 주소에 연산이 가능하므로 어떤 형태로든 응용하여 사용할 수 있다.

 

 배열은 같은 자료형의 연속적인 메모리 공간(같은 자료형 변수들의 집합)이다.

 배열의 이름은 그 배열이 시작하는 메모리 공간의 시작 주소이다.

 int arr[n]의 배열은 n개의 int 형 메모리가 만들어지며 arr[0] ~ arr[n-1]의 메모리 공간(변수)을 사용할 수 있다.

 1차원 배열의 이름은 1차원 주소의 의미를 갖고, 2차원 배열의 이름은 2차원 주소의 의미를 갖는다.

 1차원 배열의 이름에는 * 연산자를 한 번 붙이면 값이 되고 2차원 배열의 이름에는 *연산자를 두 번 붙여야 값이 된다.

 n차원 배열의 이름은 n 차원 주소의 의미이므로 * 연산자를 n 번 붙여야 값에 접근할 수 있다.

 

 

 

    포인터는 4byte 변수이고 배열은 연속적인 변수(메모리 공간)이다. 포인터와 배열은 다르다는 것을 꼭 알아 두자.

 

 

 

포인터란 주소를 저장하는 변수이므로 void형 포인터란 모든 형(char, int, double 등)의 주소를 저장할 수 있는 포인터이다.

다음 예제는 간단한 char형 주소와 int형 주소를 저장하는 void형 포인터를 선언한 경우이다.

  1. #include<stdio.h>
  2. void main()
  3. {
  4. char c = 'A';
  5. int n = 10;
  6. void *vp;
  7.  
  8. vp = &c;
  9. printf("%x %x\n", &c, vp);
  10.  
  11. vp = &n;
  12. printf("%x %x\n", &n, vp);
  13. }
  14. //////////////////////////
  15. 12ff7c      12ff7c
  16. 12ff78      12ff78

vp는 void형 포인터이기 때문에 어떤 char형 주소나 int형 주소를 저장할 수 있다. 하지만 *vp와 같이 주소가 가지고 있는 값에 접근할 수 없다. 이유는 vp(주소)가 몇 byte 크기의 메모리를 참조해야 할지 알 수 없기 때문이다.

void형 포인터 변수 vp는 자료형이 void이므로 몇 byte의 크기를 어떤 형태로(정수, 실수)로 접근해야 할지 알 수 없다. 그래서 void형 포인터 변수는 형변환해서 사용하거나 다른 포인터 변수에 대입하여 사용한다.

다음은 void형 vp를 형변환을 이용하여 값에 접근하는 예제이다.

  1. #include<stdio.h>
  2. void main()
  3. {
  4. char c = 'A';
  5. int n = 10;
  6. void *vp;
  7. vp = &c;
  8. printf("%c %c\n", c, *(char*)vp);
  9. vp = &n;
  10. printf("%d %d\n", n, *(int*)vp);
  11. }
  12. //
  13. A A
  14. 10 10

 

 

 

함수 포인터란 함수의 시작 주소를 저장할 수 있는 포인터 변수이다. 모든 함수의 이름은 그 함수가 시작하는 주소이고, 이와 같은 함수의 주소를 저장하기 위해 함수 포인터를 사용한다. 함수 포인터를 이용하면 저장된 주소로 함수 호출이 가능하다.

함수의 이름은 함수가 시작하는 주소라고 했다. 그렇다면 main() 함수의 시작 주소는 무엇일까? 함수의 이름인 'main'은 main() 함수가 시작되는 메모리의 주소이다.

다음의 예제는 main() 함수의 시작 주소를 출력한 예제이다.

  1. #include<stdio.h>
  2. void main()
  3. {
  4. printf("%x\n", main);
  5. }
  6. //////////////////
  7. 40100a

main() 함수의 시작 주소가 40100a이며 함수의 이름인 main인 것을 알 수 있다. 그러면 위와 같은 함수의 주소를 저장하려면 어떤 포인터가 필요할까? 바로 함수의 주소를 저장할 수 있는 함수 포인터가 필요하다.

이제 main() 함수의 주소를 저장하기 위한 함수 포인터를 만들어 보도록 하자. 함수 포인터는 어떻게 만들까? 함수의 원형을 알면 함수 포인터를 만들 수 있다.

cp(1).bmp 

위의 그림은 main() 함수의 주소를 저장하기 위한 함수 포인터를 선언하는 방법이다. 그림과 같이 fp는 함수 포인터이고 4byte 크기를 갖는다. 함수 포인터를 만들기 위해서는 함수의 원형과 같은 반환형과 매개 변수를 가지는 포인터 변수를 선언한다.

다음 예제는 main() 함수의 주소를 저장하기 위한 함수 포인터를 선언한 예제이다.

  1. #include <stdio.h>
  2. void main()
  3. {
  4. void (*fp) ();
  5. fp = main;
  6. printf("%x %x\n", main, fp);
  7. }
  8. ///////////////////
  9. 40100a   40100a

 

다음은 함수의 원형이 in형을 반환하고 int형 매개 변수를 2개 갖는다면 그와 같은 함수의 주소를 저장하는 함수 포인터를 선언하는 예제이다.

  1. #include <stdio.h>
  2. int func(int n1, int n2)
  3. {
  4. printf("%d %d\n", n1, n2);
  5. return n1+n2;
  6. }
  7. void main()
  8. {
  9. int (*fp)(int, int);
  10. fp = func;
  11. printf("%x %x\n", fp, func);
  12. }
  13. ////////////////////
  14. 40101e   40101e

 

함수 포인터는 함수의 주소를 저장하므로 그 주소를 이용하여 함수를 호출하는 것이 가능하다. fp와 func는 같은 함수의 주소이므로 fp를 이용하여 함수를 호출할 수 있다.

  1. #include <stdio.h>
  2. int func(int n1, int n2)
  3. {
  4. printf("%d %d\n", n1, n2);
  5. return n1+n2;
  6. }
  7. void main()
  8. {
  9. int (*fp)(int, int);
  10. fp = func;
  11. func(5,5);
  12. fp(5,5);
  13. }
  14. ////////////////////////
  15. 5   5
  16. 5   5

 

 참고

함수 포인터의 이용 목적은 크게 두 가지 정도로 나눌 수 있다. 첫 번째는 호출하는 이름(외관)을 통일하는 것이고 두 번째는 함수의 주소를 저장하는 것이다. 첫 번째는 서로 다른 함수들을 하나의 이름으로 호출하여 호출할 때마다 서로 다른 기능을 수행할 수 있도록 한다는 것이고 두 번째는 함수의 크기에 상관없이 함수의 주소 4byte만을 저장함으로써 배열의 시작 주소를 저장하여 배열 요소에 접근하듯 함수의 기능을 사용할 수 있도록 한다는 것이다.