포인터 변수

1. 사전지식

포인터 변수를 이해하기 위해서는 주소 개념에 대해서 익숙해 져야 합니다.

1) 포인터 변수의 크기

포인터 변수란 주소를 저장하는 변수이고 , c 언어에서는 변수 앞에 * 를 붙여서 선언을 합니다) (char *p, int *p , ...등등)

모든 변수가 그러 하듯 변수는 차지하는 크기가 있습니다. 문자형 변수는 1 바이트 , 정수형 변수는 4 바이트 , ... 포인터 변수는 4 바이트를 차지 합니다.

모든 컴퓨터에서 정해진 것은 아니지만 정수형 변수와 같은 크기를 포인터 변수가 가진다고 생각 하면 합니다.

2) byte machine(byte addressable)

또 하나 현존 하는 거의 모든 컴퓨터는 주소단위를 바이트 단위로 정해 두 었습니다.

즉 2 진수 8 자리 마다 하나의 주소를 매핑해 놓았습니다.

그러니 문자형 변수는 주소 분량 한개가 , 정수형 변수나 포인터 변수는 주소 분량 4 개가 있어야 하나의 정수값 혹은 주소값을 표현할 수 있습니다.

여기에서 포인터 변수가 4 바이트라는 것은 주소 공간이 2^32 - 1 개이다 .

이런 구조가 되겠죠.

다시 한번 이야기 하면 주소 공간이 32 비트이니 주소를 저장하기 위해서 4 바이트 필요 합니다.

2. char 형 포인터 변수

char *p;
char a; 
a = 'A';
p = &a;  //&:주소 연산자 
printf("%c %c",a,*p); //*:간접 연산자

첫 프로그램이니 한 문장 한 문장 메모리 맵을 그려 보겠습니다.

1, char *p;

p 는 포인터 변수 4 바이트를 차지 합니다. 그런데 *p 는 문자형이어서 한 바이트 크기를 의미합니다. 포인터 변수를 이해 하는데 이 부분이 중요 합니다.

p 는 4 바이트 , *p 는 1 바이트

*p 에 대해서는 4 번 문장에서 설명하기로 하고 , 일단 p 는 4 바이트를 차지하고 메모리에 공간이 잡힙니다. (p 가 잡히는 시작번지를 1111 1111 1111 1111 1111 1111 0000 0000 로 가정하겠습니다)

2. char a;

a 는 문자형으로 1 바이트 변수이니 1 바이트를 차지 합니다.(a 가 잡히는 번지를 1111 1111 1111 1111 1111 1111 1111 1111 로 가정하겠습니다.)

3. a = 'A';

'A' 가 ascii 코드로 16 진수 41 이니 0100 0001 값이 a 영역에 잡힙니다.

4. p = &a;

& 는 주소 연산자 입니다. 변수 a 의 주소 값을 변수 p 에 대입하라는 것입니다. p 가 주소를 담을 수 있는 포인터 변수이니 주소 값을 대입하는데 문제가 없겠죠.

a 는 0x41 을 &a 는 주소값 111....1 을 의미 합니다.

아래 프로그램을 실행하면 같은 값이 나옴을 확인 할 수 있습니다.

   char *p,a;

   a = 'A';
   p = &a;

   printf("%x %x\n",&a,p);
즉 a 의 주소값을 p 가 가지면 , a 와 *p 는 일심동체가 됩니다. 이를 별명을 가진다는 의미로 aliasing 이라 합니다.

c 언어는 parameter passsing 방법은 변수의 주소가 아닌 값을 넘기는 call by value 방법을 사용하지만 aliasing 을 이용해서 값을 변경할 수 있습니다.

char *p,a;

a = 'A';
p = &a;
*p = 'B';
printf("%c",a);
이렇게
void f(char* p)
{
   *p = 'B';
}

int main()
{
   char a;
   a = 'A';
   f(&a);
   printf("%c",a);
}

5. printf("%c %c",a,*p);

*p 가 나왔습니다. 이 * 는 간접 연산자 입니다. p 가 가지고 있는 주소값으로 접근하라 는 것입니다.

조금 전에 *p 는 1 바이트라고 했다. 그래서 p 가 가지고 있는 주소 1111 111....1 의 내용 한 바이트의 내용을 의미 합니다. 즉 0x41 이 됩니다.

&p , p ,*p 는 그림과 같습니다.

포인터 변수에 * 를 붙힌 크기 만큼 접근 한다.

3. int 형 포인터 변수

정수형 포인터를 한 번 보겠습니다.

int *p,a;
a = 20;
p = &a;
printf("%d %d",a,*p);

문자형 pointer 와 같은 점은 p 는 4 바이트 포인터 변수이고

다른 점은 *p 는 int 형이니 4 바이트 크기입니다. 변수 a 가 정수형 변수로 4 바이트 걸쳐 20 값이 있으니 *p 를 하면 4 바이트 분량의 값으로 접근해야 정상적인 접근이 됩니다.


포인터 변수의 연산에 대해서 알아 봅니다.포인터 값을 1 증가 혹은 감소하면 어떻게 될까요?

아래 프로그램을 실행해서 결과 값을 확인 해 봅니다.

   char *p,a;
   int *q,b;

   p = &a;
   q = &b;

   printf("%x %x\n",p,p+1);
   printf("%x %x\n",q,q+1);
p+1 은 p 보다 1 많고 , q+1 은 q 보다 4 많은 것을 확인 할 수 있나요?

즉 포인터 변수(값) 에 * 를 붙인 크기만큼 접근하고 , 증가 혹은 감소시 * 를 붙인 크기 만큼 증가 혹은 감소 한다.

이렇게 되어야 정상적으로 사용할 수 있겠지요. 이 두가지 사항이 포인터 변수의 이해에 가장 중요한 사항 입니다.

4. 다중 포인터

char **p;

*p 는 4 바이트로 접근하고 p 를 1 증가하면 4 씩 증가 , **p 는 1 바이트 접근 *p 를 1 증가하면 1 씩 증가.

더블 포인터를 이용한 간단한 예시 프로그램.

char **p,*q;
char a;

q = &a;
p = &q;
a = 'A';

printf("%c %c %c",a,*q,**p);

이 프로그램으로 여러가지 테스트를 한 번 해 보세요.

5. struct(class) 형 포인터

struct stud
{
   int num;
   char name;
};

int main()
{
   stud *p,a;

   a.num = 20;
   a.name ='A';
   p = &a;
   printf("%d %c",p->num , p->name);
}

p 는 4 바이트 , *p 는 5 바이트(정수형 + 문자형)

*p 를 하면 두 개의 멤버를 접근하게 되고 , 이 중 하나의 멤버로 접근하기 위해서는 *p.num , *p.name 을 하면 될 것 같습니다. 그런데 * 보다 . 이 우선순위가 빨라 (*p).num , (*p).name 으로 접근 해야 합니다.

이렇게 접근하려니 괄호도 사용하고 ....등등으로 불편해서 -> 연산자를 도입합니다.

(*p).num 은 p -> num 
(*p).name 은 p -> name  

6. 메모리 할당 함수

아래와 같은 사용은 하지 말아야 합니다.
char *p;

*p = 'A';
printf("%c",*p);

p 에 안전한 기억장소가 할당되지 않았습니다.

안전한 기억 장소를 할당하기 위해서 메모리 할당함수를 사용합니다. c 언어에는 malloc , calloc 함수가 있고 , c++ 에는 new 가 있습니다.

char *p;

// 메모리 할당  new char = new char[1] 입니다. 연속적일 할당을 받기 위해서는 new char[숫자]
p = new char;
*p = 'A';
printf("%c",*p);

배열을 이용할 때 보다 장점은 메모리 할당은 실행시간에 이루어져서 , 배열 보다는 유연하게 사용할 수 있는 장점이 있습니다.

도둑놈이 어떤 집에 물건을 훔치러 갈 때 그 집에 보석이 얼마나 있는지 알수 없어니 배열을 이용하면 가져갈 수 있는 만큼 큰 가방을 가져 갔는데 보석이 없어면 허탈해 지겠죠. 메모리 할당을 이용하면 들어가서 보고 그 때 그 때 가방의 크기를 늘릴 수 있습니다.


[질/답]
[홈으로]  [뒤 로]
[푼 후(0)]