대학교에 다니면서 잠시나마 적었던 C언어 기초 강좌 입니다.
그나마 Pointer는 정성들여 썼길래 이 blog에 옮겨서 적어 놓습니다.
14강 2부 포인터의 개념
그럼 본격적으로 포인터에 대해서 알아 보도록 하자. 우선 C언어 책에 나와있는 정의를 보고 넘어가자.
포인터란? 값자체가 아니라 그 값이 있는 곳의 주소가 저장되어 있는 변수
역시 어렵다 ㅡㅡ; 일반적으로 우리가 지금까지 사용해온 변수는 어떠한 데이터값 즉 작은수 35, 큰수 90451234, 소수점 0.0432 혹은
문자 ‘A’ 와 같은 데이터 값을 저장하는 용도로 써왔다.
그런데 포인터란 어떤 특정한 데이터값이 아닌 주소를 저장하는 변수의 형을 뜻한다.
지난 14강 1부 시간에 우리가 변수의 주소에 대해서 배운 것을 기억 할 것이다. 즉 포인터는
0x0012FF7C와 같은 주소값을 저장한다. 일반적인 포인터 변수의 선언은 아래와 같은형식으로 한다.
int *pA1;
참고적으로, 일반적으로 다른 일반 변수와 구분하기 위해 포인터 변수일경우 이름앞에 p를 붙인다.
int형 변수를 선언하는 것과 똑같은데 다른점은 변수 pA1앞에 *
기호가 있다는 것이다.
*
는 포인터 연산자라고 부르며 변수선언시에 *
를 앞에 붙여주면 이 변수를 포인터 변수로 선언한다는 의미다. int에 *
를 붙이듯
다른 변수형에 모두 *
를 붙이면 포인터 변수가 된다.
double *pD; long *pL;
이렇게 선언된 포인터 변수에 주소값을 집어 넣어보자.
int a1 = 3; int *pA; pA = &a1;
우선 int a1=3; 에서 a1이라는 int형 변수를 선언후 a1에 3을 저장한다. 그리고 pA라는 포인터 변수를 하나 선언했다.
다음 pA에 &a1을 대입한다. 여기서 &a1은 a1변수의 주소값인 0x0012FF7C가 되는 것이고, 이 주소값을 pA에 저장하게 된다. 실제 pA의
값을 출력하면 0x0012FF7C가 된다.
int a1 = 3; int *pA; pA = &a1; int a2; a2 = *pA;
위 예제에 2개의 문장이 추가가 되었다. int형 변수 a2를 선언하고 a2에 *pA를 대입하고 있다.
*
(포인터)의 의미는 변수를 선언할때는 포인터 변수선언이라는 뜻이지만 그외에는 pA가 저장하고 있는 주소가 가리키는 데이터 값을 뜻
한다. 현재 pA는 0x0012FF7C라는 주소값 즉 변수 a1의 주소값을 가지고 있다. 그렇다면 *pA는 0x0012FF7C 주소번지에 저장된 데이터
값, 즉 3 이라는 값을 뜻하게 된다. 그리하여 a2에는 3이라는 값이 저장되게 된다.
지금 까지 설명 한것을 직접 눈으로 보도록 하자.
현재 int a1=3; int *pA; 까지의 상태는 a1의 값이 3, &a1(a1의주소값)이 0x0012FF7C이다.
pA의 데이터 값은 일반변수와 마찬가지로 현재 선언만 되어 있는 상태이기에 0xCCCCCCCC라는 값이 저장되어 있다.
pA = &a1;
을 실행후 pA의 값에 a1의 주소값인 0x0012FF7C라는 값이 저장된 것을 볼수 있다.
a2 = *pA; 후에 a2에 데이터값 3이 저장되는 것을 볼수 있다. 이는 단순한 3이라는 값을 a2변수의 저장공간에 복사만 할뿐 a2와 pA간
에 주소값은 서로 다른것을 볼수 있다.
pA = 0x0012FF7C, &a2 = 0x0012FF74
지금까지 가장 기본적인 포인터를 사용해 보면서, int형 포인터 변수를 이용해 보았다. 다른 포인터형 변수라고 다를것은 없다. int형이
든, double형이든, char형이든 모든 포인터 변수는 단지 주소만을 저장 할 뿐이다. 그렇다면 이 포인터형의 크기는 결국 0x0012FF7C라
는 주소만을 저장하면 되기 때문에 그 종류에 상관없이 크기가 4Byte면 충분하다. 여기서 포인터형변수가 왜 4byte인지 의문점이
해결된다.
다음으로 포인터를 사용해서 배열을 다루는 것에 대해 알아보자.
배열은 이미 예전 강좌에서 설명을 했었다. 필요한 부분만 다시한번 짚고 넘어가면,
int a[3];
int형 배열 a를 선언하는 선언문이며, 여기서 배열의 이름인 a라는 것은 a배열의 시작주소값을 뜻한다고 했었다. 시작주소값이라고 하면
0x0012FF74같은 주소를 의미하는 것인데… 그럼 이것을 포인터 변수에 넣을 수 있지 않을까?
한번 시도해 보자.
int a[3]; a[0] = 1; a[1] = 2; a[2] = 3; int *pA; pA = a;
int a[3]; 배열 선언후 각각 1,2,3이라는 값을 삽입했다. 배열의 이름인 a의 값을 출력해보니 역시 우리가 공부했던대로 배열의 시작주소
값인 0x0012FF74라는 값을 보여주고 있다.
우리의 예상대로 포인터 변수 pA에 a배열의 시작주소값인 0x0012FF74가 저장되는 것을 눈으로 확인할 수 있다.
그렇다면 여기서 *pA의 값을 출력하면 어떻게 될까? *pA는 pA가 가지고 있는 주소값이 가리키는 데이터 값이라 설명했다. 그렇다면 현
재 pA는 a의 배열의 시작주소값이므로 a[0]의 주소값(&a[0])을 가지고 있다고 할수 있다. 즉 *pA는 a[0]의 값인 1이 출력된다.
그렇다면 포인터 변수를 이용해서 a배열의 시작인 a[0]말고 a[1]이나 a[2]에 접근 할수 있을까?
방법은 아래 방식을 이용한다.
pA + 1
포인터 변수 pA + 1은 pA가 가리키고 있는 주소값에 포인터 변수의 형(int)의 크기만큼 더하게 된다. 즉 현재 pA가 0x0012FF74라고 하면
+1은 int형이 4byte이기 때문에 pA+1은 0x0012FF78의 주소값을 가지게 된다. 연속적으로 pA+2는 4byte*2 인 8btyte를 더한 0x0012FF7C
가 되는 것이다.
기존에 배열에 관해 배웠을때 배열에 할당된 메모리는 연속적으로 위치해 있다고 했다. 그렇다면 a[0]다음 4byte가 a[1]이듯 현재 pA
가 a[0]의 주소값을 가지고 있다면 pA+1은 a[1]의 주소를 가지게되고 pA+2은 a[2]의 주소를 가지게 된다.
실제 결과를 보자.
아마 많은 분들이 포인터에 기본적인 방법들인 pA는 주소값을 저장한다던가 *pA가 그 주소가 가리키는 값이라는 것은 쉽게 이해하지만
배열을 포인터변수에 집어넣는것과 같은 곳에서 많이 난해해 할것이다. 이것을 좀더 이해하기 쉽게 하는 것은 아래와 같은 방식으로 이
해 해보도록 하자.
int a[2]; a[0] = 11110; a[1] = 4440; char *pA; pA = (char*)a;
기존에 보던 방식과 약간다른 점이 있다. int형 배열의 시작주소인 배열의 이름 a를 char형 포인터 변수 pA에 삽입한다. 이것은 단순하게
이해하는데 도움을 주고자 하는것이기에 컴파일 오류를 없애기 위해 (char*)가 들어갔다고만 생각하고, 단순하게 int형 배열의 주소를
char형 포인터 변수에 저장했다고만 생각하자
이것을 실행한 결과화면은 아래와 같다.
a의 값은 배열의 시작주소이기에 pA에 저장된 값인 0x0012FF78일것이다.
그렇다면 *pA는 char형 포인터변수가 가리키는 값을 의미한다. 메모리상에서 0x0012FF78 번지의 값인 66(16진수), 102(10진수) 가 출력
되는것을 확인할수 있다.
여기에 pA+1은 char형의 크기만큼 즉 1byte만큼 주소를 증가 시킨값이기에 0x0012FF79번지 가 된다. 그 번지에 저장된 값은 2B(16진수)
즉 10진수 43이다. 마찬가지로 pA+4는 0x0012FF78+4 = 0x0012FF7C번지를 뜻하면 그 값은 58(16진수), 10진수로는 88이다.
위와 같이 포인터라는 것은 메모리의 주소를 프로그래머가 손쉽게 접근할수 있도록 해준다.
포인터와 관련된 소스코드를 보면서 이해하기에는 너무 난해한 어려움이 있다.
그것을 위 예제와 같이 메모리를 직접 그려보고 실제 메모리 주소에서 데이터 값이 어떻게 변하는지를 생각한다면 조금은 이해
하는데 도움이 될 수 있을것이다.
[…] 힘들게 1부(http://scanhand.cafe24.com/226-2), 2부(http://scanhand.cafe24.com/pointer-part-2)를 통해서 포인터란 무엇인지 […]