2) 포인터의 활용 예제

 

1. 주소 값의 이해와 표현

포인터(Pointer)란 메모리의 주소 값을 담고있는 변수 혹은 상수이다. 다른 말로, 데이터의 위치를 가리키는 인자라고 할 수도 있다. 의외로 간단해 보일지도 모르겠지만 주소 값과 관련이 있어 메모리의 주소체계를 이해하지 못하면 포인터를 정확히 이해할 수 없다. 여기서 주소란 그 메모리의 저장장소의 위치를 나타내는 값으로 하나의 주소값은 1바이트 크기의 메모리 공간을 표현한다.

<한 블럭(주소)당 1바이트의 메모리 공간 차지, 한개의 주소는 8개의 비트가 묶임>


그렇다면 16비트의 주소 값의 범위는 어디까지 일까요? 2의 16은 65,535이므로 16비트로는 총 65536개의 바이트에 주소를 부여할수 있게 된다. 만약 주소값의 시작이 0번지 부터라면 65535번지까지 주소를 부여할 수 있습니다. 32비트 같은 경우에는 2의 32승, 최대 4,294,967,296(4GB) 바이트까지 주소를 부여할수 있게 되는것이다. 

 

2. 포인터 변수 선언


포인터 변수란 메모리 주소를 저장하는 변수이며 데이터 타입과 식별자(=변수명) 사이에 그저 * 하나만 넣어주면 포인터 변수가 된다. 기본적인 데이터 타입과 구조체, 배열, 공용체에 대해서도 포인터형을 만들수가 있다. 그리고 & 연산자를 변수명 앞에다 가져오면 그 변수의 주소값을 반환하게 된다.

 


 

결과:

num의 저장 위치: 0x22ff44
num1의 저장 위치: 0x22ff40
num2의 저장 위치: 0x22ff3c
계속하려면 아무 키나 누르십시오 . . .

 

10, 11, 12행에서 변수명 앞에 & 연산자를 붙여주어 그 변수의 주소값이 반환되고 16진수의 형태로 출력한다는 의미다. %x는 16진수로 출력시킬때 사용하는 출력 포맷이고, 그 가운데에 들어간 #를 제외하면 앞에 0x가 붙지 않습니다. & 연산자는 '어떤 변수의 주소를 알아내는 역할' 도 하는 연산자이며 상수는 메모리에 위치하지 않으므로 주소가 없으며 & 연산자를 사용할 수 없다.

포인터 변수를 이용하면 프로그램이 간결하고 효율적이며 그 포인터가 가리키는 변수의 자료형에 따라 타입을 맞추어 선언해야 한다. 다음은 포인터 변수에 관한 예제이다.

#include <stdio.h>
 
int main()
{
    int num, num1, num2;
     
    num=50;
    num1=72;
    num2=94;
    printf("num의 저장 위치: %#x\n", &num);
    printf("num1의 저장 위치: %#x\n", &num1);
    printf("num2의 저장 위치: %#x\n", &num2);
    return 0;
}

결과:

변수 Number의 값: 50
변수 Number의 주소값: 0x22ff44


변수 Number의 값: 60
계속하려면 아무 키나 누르십시오 . . .

 

앞에서 말했듯이, 포인터 변수는 주소값만 저장할 수 있습니다. 지금까지 우리가 배운 * 연산자의 기능은 곱셈을 할때 사용하거나, 포인터 변수 선언시에도 사용되었다. 그런데 14행에서의 * 연산자는 무슨 기능을 할까? 이것은 간접 참조 연산자로 단항 연산자로 사용되면 이 포인터가 가리키는 메모리 공간의 접근을 의미한다. 즉, 14행의 문장에서는 pNumber이 가리키는 변수 Number를 의미하며 이것은 'Number=60'과 동일한 기능을 합니다. 이 간접 참조 연산자는 포인터 변수를 초기화 하고 사용해야 한다.

 

주의할 점은 여러개의 포인터 변수를 한번에 선언할때 변수마다 *를 붙여주어야 한다. 

1

int *a, *b, *c;

아래와 같이 선언해버리면, b와 c는 정수형 포인터가 아닌 정수형 변수가 되어버린다.

1

int *a, b, c;

 

3. 포인터 연산


포인터끼리 더하거나 뺄수 있으며 포인터에 정수를 더하거나 빼거나, 대입마저도 가능하다. 그렇지만 포인터끼리 더하는건 원칙적으로 허용하지 않고 아무 의미가 없으며, 더하려고 시도하면 에러를 내보낸다. 이것은 불가능 해서가 아니라, 단순히 프로그래머가 실수해서 그런 코드를 적었을 확률이 높기 때문이다. 물론 굳이 더하려고 한다면 더할수 있다. 다만 포인터와 포인터 끼리의 덧셈은 아무 의미도 없다.

한번, 포인터와 포인터를 서로 더하고 뺀 후의 결과를 출력하는 예제를 보도록 합시다.

#include <stdio.h>
 
int main()
{
    char Array[]="Pointer Array";
    char  *pArray1, *pArray2, *pArray3;
     
    pArray1=&Array[0];
    pArray2=&Array[11];
    pArray3=(char *)((unsigned)pArray1+(unsigned)pArray2); // 아무 의미가 없음! 
    printf("Array1의 주소 값: %#x\n", pArray1);
    printf("Array2의 주소 값: %#x\n\n", pArray2); 
    printf("Array1과 Array2의 주소를 더한 값: %#x\n", pArray3);
    printf("Array1(%c)과 Array2(%c)의 거리: %d\n", *pArray1, *pArray2, pArray2-pArray1); 
    return 0; 
}

 

결과:

Array1의 주소 값: 0x22ff30
Array2의 주소 값: 0x22ff3b

Array1과 Array2의 주소를 더한 값: 0x45fe6b
Array1(P)과 Array2(a)의 거리: 11
계속하려면 아무 키나 누르십시오 . . .

 

10행의 포인터끼리의 덧셈 연산은 아무 의미도 없다. (포인터 끼리의 곱셈 또는 나눗셈도 포함한다). 그렇지만 뺄셈은 가능합니다. 포인터 끼리의 뺄셈은 두 포인터 간의 거리를 나타냅니다. 14행에서 P와 a의 거리는 11이며, 그 사이에 'o', 'i', 'n', 't', 'e', 'r', ' ', 'A', 'r', 'r'가 존재함을 확인할 수 있습니다.

만약, 포인터에 정수를 더하거나 빼는 문장을 거친다면 어떤 결과가 출력될까요?

#include <stdio.h>
 
int main()
{
    char *pc;
    int *pi;
    double *pd;
     
    pc=(char *)100;
    pi=(int *)100;
    pd=(double *)100;
     
    printf("pc 증가 전: %d\n", pc);
    printf("pi 증가 전: %d\n", pi); 
    printf("pd 증가 전: %d\n\n", pd); 
    pc++;
    pi++;
    pd++;
    printf("pc 증가 후: %d\n", pc);
    printf("pi 증가 후: %d\n", pi); 
    printf("pd 증가 후: %d\n", pd); 
    return 0;
}

 

결과:

pc 증가 전: 100
pi 증가 전: 100
pd 증가 전: 100


pc 증가 후: 101
pi 증가 후: 104
pd 증가 후: 108
계속하려면 아무 키나 누르십시오 . . .

 

결과를 보시면 char는 1, short는 2, int는 4, float는 4, double는 8로 포인터의 자료형의 크기만큼 증가한다는 것을 알수 있다. 만약에 2 이상의 수를 더한다면 '포인터가 가리키는 변수 데이터 타입의 크기 * 정수' 만큼 증가가 되는걸 보실수 있습니다. 뺄셈도 이와 마찬가지다.

포인터에 덧셈과 뺄셈을 하는것 말고도, 포인터끼리의 비교도 가능하다. 

  #include <stdio.h>
   
  int main()
  {
      int Num1, Num2;
      int *pNum1, *pNum2;
       
      pNum1=&Num1;
      pNum2=&Num2;
       
      if(pNum1!=NULL) printf("pNum1은 NULL이 아닙니다.\n");
      if(pNum1!=pNum2) printf("pNum1과 pNum2은 다릅니다.\n");
      if(pNum1<pNum2) printf("pNum1은 pNum2보다 앞에 있습니다.\n");
      else printf("pNum1은 pNum2보다 뒤에 있습니다.\n"); 
      return 0;
  }

 

pNum1은 NULL이 아닙니다. pNum1과 pNum2은 다릅니다. pNum1은 pNum2보다 뒤에 있습니다. 계속하려면 아무 키나 누르십시오 . . .

 

참고로, 11행에서의 비교는 pNum1이 널 포인터(NULL Pointer)인지를 확인하기 위해서 이다. 널 포인터는 아무곳도 가리키지 않는 포인터를 말한다.  

4. 포인터 배열

포인터 배열(Pointer Array)란 말 그대로 포인터 변수로 이루어진 배열을 말하는 것이며 포인터 배열의 선언방식은 우리가 알고있는 배열 선언방식과 크게 다르지 않다.

#include <stdio.h>
 
int main()
{
    int num1, num2, num3;
    int *pNumArray[3]={&num1, &num2, &num3};
    int i;
     
    for(i=0; i<3; i++) 
     scanf("%d", pNumArray[i]);
      
     printf("입력된 숫자는 각각 %d, %d, %d 입니다.\n", *pNumArray[0], *pNumArray[1], *pNumArray[2]);
     return 0;
} 

 

결과:

4
5
7
입력된 숫자는 각각 4, 5, 7 입니다.
계속하려면 아무 키나 누르십시오 . . . 

 

6행에서 길이가 3인 포인터 배열 pNumArray를 선언후 각각 num1, num2, num3의 주소값으로 초기화 시켰다. 

 

 

01   #include <stdio.h>
02   #include <stdlib.h>
03   #define MAX 5
04
05   int main()
06   {
07       int i = 10;  
08       int *p;
09       
10       p=&i;
11       printf("i = %d\n", i);
12
13       *p = 20;
14       printf("i = %d\n", i); 
15
16       system("pause");
17       return 0;
18   }

 

예제를 연습해 봅시다