C언어 - 포인터의 이해(1)
1. 포인터란 무엇인가?
포인터는 두 가지로 나누어서 설명할 수 있다. 포인터는 변수와 상수로 나눌 수 있다. (포인터변수, 포인터상수)
포인터라는 형태의 변수를 선언을 할 수도 있고, 상수를 선언할 수도 있다.
우선 포인터 변수를 먼저 살펴보자.
포인터 변수는 주소값을 저장하기 위한 변수이다. 여기서 주소값은 정수일까? 아니면 실수일까?
예를들어 살펴보자.
CPU가 32bit 시스템이다 (여기서 CPU가 32bit 시스템이라는 뜻은 CPU가 한 번에 연산할 수 있는 데이터의 크기가 32bit이고, 한 번에 이동할 수 있는 데이터의 크기가 32bit라는 것이다.)
즉, 32bit 시스템에서는 주소값을 32bit 시스템으로 표현한다.
마찬가지 논리로 근래에 우리가 많이 사용하는 64bit 시스템에서도 주소값을 64bit 시스템으로 표현한다.
32bit 시스템같은 경우는 내가 할당할 수 있는 주소값의 크기가 2^32이다.
마찬가지로 64bit 시스템 같은 경우도 할당 가능한 주소값의 크기가 2^64이다.
위와 같은 말이 무슨 의미일까?
2^32 크기에 해당하는 바이트 크기만큼, 혹은 2^64 크기에 해당하는 바이트 크기만큼 메모리 공간에 주소값을 부여할 수 있다는 말이다. 즉, 주소값은 정수이다.
그렇다면 이 주소값을 어디에다가 저장할 수 있을까?
주소값은 모양이나 생김새가 정수와 같다. 즉, int형 변수에도 저장이 가능하다.
하지만 int형 변수에도 저장이 가능한데 굳이 포인터 변수를 선언해서 저장하는 이유가 무엇일까?
이게 우리가 가져야 할 첫 번째 의문이다.
이러한 의문을 일단은 간직한 채로 다음을 한번 봐보자 (의문은 곧 해결된다)
예를 들어
int num = 10;
~~~~~~~ = #
num이라는 변수의 주소값을 저장하기 위해 이러한 형태의 코드가 있다고 가정하자.
num의 주소값을 어딘가에 저장하기 위해서 이때 사용되는 연산자가 num 앞에 '&' 연산자를 붙여주면 된다.
(여기서 num 앞에 &를 붙여주는 것은 우리가 C언어를 처음 공부할 때 scanf 함수에서 많이 사용했을 것이다.)
여기서 & 연산자를 붙여주게 되면 num이라는 변수의 주소값이 반환될 것이다.
이때 여기서 반환되는 주소값을 앞서 얘기했듯 int 형 변수에다 저장할 수도 있지만,
int형 변수의 주소값 저장은 int 형 포인터 변수에다가 저장해야 된다.
그렇다면 다음 내용이 대충 예상되지 않는가?
double형 변수의 주소값 저장은 double형 포인터 변수에다가 저장해야 한다.
char형 변수의 주소값 저장은 char형 포인터 변수에 저장해야 한다.
type형 변수의 주소값 저장은 type형 포인터 변수에 저장해야 한다.
int형 변수의 주소값이나 혹은 double형, char형, type형 변수의 주소값은 왜 각각 int형 포인터 변수, double 형 포인터 변수, char형 포인터변수, type형 포인터 변수에 저장을 하는가?
이것이 우리가 가질 두 번째 의문이다.
2. 포인터 변수 선언하기
일단은 앞선 두가지 의문을 가지고 이어나가보자.
앞서 얘기했듯 포인터 변수는 주소 값 저장을 목적으로 선언된다. 주소 값은 변수의 자료형이 무엇이냐에 따라 형태가 달라지는것은 아니다. 값의 차이가 있을 뿐이지, 형태는 차이가 없다.
다음으로 포인터 변수와 '&'연산자 를 한번 살펴보자
"정수 7이 저장된 int 형 변수 num을 선언하고 이 변수의 주소 값 저장을 위한 포인터 변수 pnum을 선언하자. 그리고 나서 pnum에 변수 num의 주소 값을 저장하자"
위의 내용을 코드로 옮기면 아래와 같이 된다.
#include<stdio.h>
int main(void) {
int num = 7;
int* pnum;
pnum = #
......
}
포인터 변수 pnum을 선언하고 num의 주소 값을 pnum에 저장한 것이다.
위와 같은 코드를 메모리상의 상태를 그림으로 표현하면 아래와 같다.
이러한 상태를 다음과 같이 표현한다.
포인터 변수 pnum이 변수 num을 가리킨다.
가리키고자 하는 변수의 자료형의 따라서 포인터 변수의 선언방법에는 차이가 있다.
포인터 변수에 저장되는 값은 모두 정수로 값의 형태는 모두 동일하지만, 선언하는 방법에 차이가 있다.
(이유는 메모리 접근과 관련이 있다.)
int* pnum1;
//int* 는 int형 변수를 가리키는 pnum1의 선언을 의미함
double* pnum2;
//double* 는 double형 변수를 가리키는 pnum2의 선언을 의미함
unsigned int* pnum3;
//unisgned int* 는 unsigned int 형 변수를 가리키는 pnum3의 선언을 의미함
이것들을 일반화 하면 아래와 같다.
type* ptr;
//type형 변수의 주소 값을 저장하는 포인터 변수 ptr의 선언
다음으로 포인터의 형(Type) 에 대해서 살펴보자
int* //int형 포인터
int* pnum1; //int형 포인터 변수 pnum1
double* //double형 포인터
double* pnum2; //double형 포인터 변수 pnum2
마찬가지로 이것들을 일반화하면 아래와 같다.
type* //type형 포인터
type* ptr; //type형 포인터 변수 ptr
마지막으로 포인터 변수 선언에서 * 의 위치에 따른 차이는 없다. 즉, 다음 세 문장은 모두 동일한 포인터 변수의 선언문이다.
int * ptr; //int형 포인터 변수 ptr의 선언
int* ptr; //int형 포인터 변수 ptr의 선언
int *ptr; //int형 포인터 변수 ptr의 선언
이번 포스트에서는 포인터에 대해 기본적으로 간략하게만 살펴 보았다.
다음 포스트에서 포인터에대해서 조금 더 깊은 이해와 앞서 설명했던 우리가 가지게 된 두가지 의문점에 대해서 해결하겠다.