포인터 변수를 이해하기 위해서는 주소 개념에 대해서 익숙해 져야 합니다.1) 포인터 변수의 크기
포인터 변수란 주소를 저장하는 변수이고 , c 언어에서는 변수 앞에 * 를 붙여서 선언을 합니다) (char *p, int *p , ...등등)모든 변수가 그러 하듯 변수는 차지하는 크기가 있습니다. 문자형 변수는 1 바이트 , 정수형 변수는 4 바이트 , ... 포인터 변수는 4 바이트를 차지 합니다.
모든 컴퓨터에서 정해진 것은 아니지만 정수형 변수와 같은 크기를 포인터 변수가 가진다고 생각 하면 합니다.
2) byte machine(byte addressable)
또 하나 현존 하는 거의 모든 컴퓨터는 주소단위를 바이트 단위로 정해 두 었습니다.즉 2 진수 8 자리 마다 하나의 주소를 매핑해 놓았습니다.
그러니 문자형 변수는 주소 분량 한개가 , 정수형 변수나 포인터 변수는 주소 분량 4 개가 있어야 하나의 정수값 혹은 주소값을 표현할 수 있습니다.
여기에서 포인터 변수가 4 바이트라는 것은 주소 공간이 2^32 - 1 개이다 .
이런 구조가 되겠죠.
다시 한번 이야기 하면 주소 공간이 32 비트이니 주소를 저장하기 위해서 4 바이트 필요 합니다.
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 는 그림과 같습니다.
포인터 변수에 * 를 붙힌 크기 만큼 접근 한다.
정수형 포인터를 한 번 보겠습니다.
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 많은 것을 확인 할 수 있나요?
즉 포인터 변수(값) 에 * 를 붙인 크기만큼 접근하고 , 증가 혹은 감소시 * 를 붙인 크기 만큼 증가 혹은 감소 한다.
이렇게 되어야 정상적으로 사용할 수 있겠지요. 이 두가지 사항이 포인터 변수의 이해에 가장 중요한 사항 입니다.
char **p;
- p 는 4 바이트(p 앞에 * 가 있으니 포인터 변수)
- *p 는 4 바이트 ( 앞에 * 가 있으니)
- **p 는 1 바이트 ( 앞에 char 이니)
*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);이 프로그램으로 여러가지 테스트를 한 번 해 보세요.
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
아래와 같은 사용은 하지 말아야 합니다.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);배열을 이용할 때 보다 장점은 메모리 할당은 실행시간에 이루어져서 , 배열 보다는 유연하게 사용할 수 있는 장점이 있습니다.
도둑놈이 어떤 집에 물건을 훔치러 갈 때 그 집에 보석이 얼마나 있는지 알수 없어니 배열을 이용하면 가져갈 수 있는 만큼 큰 가방을 가져 갔는데 보석이 없어면 허탈해 지겠죠. 메모리 할당을 이용하면 들어가서 보고 그 때 그 때 가방의 크기를 늘릴 수 있습니다.