포인터를 이해하기 위한 첫 번째 단계는 포인터가 기계 수준에서 어떤 것을 나타내는 것인지 시각화 하는 것이다. 대부분의 현대 컴퓨터에서, 메모리는 바이트로 나누어져 있다. 그리고 각 바이트는 8개의 비트로 이뤄져 있다.
각 바이트는 구분을 위한 고유의 메모리 주소를 가지고 있다. 만약 n개의 바이트가 메모리가 있다면 0부터 n-1까지 범위 내에서 주소가 있을 것이다.
실행가능한 프로그램은 C프로그램에 대응하는 기계어 코드와 원본 프로그램 변수에 저장되어 있는 데이터 두 개로 구성되어 있다. 프로그램의 각 변수는 한 개 이상의 바이트를 메모리에서 차지한다. 이때, 첫 번째 바이트의 주소가 해당 변수의 주소라고 불린다. 즉 변수의 주소는 변수의 시작점이다.
여기서 포인터가 등장한다. 주소가 숫자로 나타나기는 하지만, 이 값의 범위는 정수 범위 내에서 매우 다양하게 나타난다. 그러나 우리는 특별한 포인터변수에 이것들(주소들)을 저장할 수있다.
포인터 변수를 선언하는 것은 일반적인 변수를 선언하는 것과 다를 것이 없다. 다만 *이 이름 앞에 들어간다.
int *p;
여기서 p는 int 타입의 객체를 포인팅하는 포인터 변수다. 즉 double, char 등에 대해서도 활용이 가능하다.
포인터 연산자를 선언하는 것은 포인터를 위한 공간을 메모리에서 확보할 뿐, 어떤것을 포인팅 할 지정하지는 않는다.
int i, *p;
p = &i;
변수 i의 주소를 p에 할당 함으로써, p는 i를 포인팅한다. 물론 선언과 동시에 초기화를 수행해 줄 수 있다.
int i, *p = &i;
만약 *p를 출력하면 i의 주소가 나올까? 아니다 i의 값이 나온다. 즉 *은 그 뒤의 주소에 위치한 값을 반환한다. 그 결과 &과 *은 서로 상쇄될 수 있다.(물론 순서는 중요하다.) *p는 i를 지칭하는 별명이다.
[주의] 간접참조연산자를 초기화 되지 않은 포인터에 적용하지 말라. 그렇지 않으면 정의되지 않은 행위가 나타날 수 있다.
p = &i;를 통해 포인터 할당을 할 수 있다. 이 때, i의 주소가 p에 복사되었다. 그 후 q = p;를 통해서 주소를 복사할 수 있다. 즉 같은 주소에 대한 포인터가 두 개 만들어졌다. q = p;와 *q = *p;를 혼동하지 않도록 주의하라.
앞서 함수 부분에서 살펴 봤듯, 매개변수에 주어지는 변수는, 함수 내부에서 수정이 불가능하다. 이는 입력값이 복사되어 매개변수로 전달되고, 함수 구문이 실행되는 것이기 때문이다. 만약 입력변수를 함수에서 수정하고 싶다면, 입력변수의 주소, 즉 포인터를 활용하면 된다.
void decompose(double x, long *int part, double *frac_part)
{
*int_part = (long) x;
*frac_part = x - *int_part;
}
decompose(3.14159, &i, &d);
사실 포인터를 매개변수로 활용하는 것은 초반부에서도 있었다. scanf가 바로 그것이다.
만약 포인터가 매개변수로 등장한다면, 우리는 그 함수가 해당 주소의 값을 변경하려는 목적을 가지고 있다고 추정할 수 있다. 그러나 단순히 효율성을 위해 포인터르 매개변수로 사용했을 수도 있다. 이 경우, 포인터가 가리키는 주소의 값이 변경되는 것을 원치 않을 수도 있다. 이 경우 const를 매개변수 맨 앞에 붙어주면 된다.
포인터도 다른 것들과 마찬가지로 함수에서 반환 될 수 있다. 지역변수에도, 전역변수에도, static 선언된 지역변수에도 사용될 수 있다.
[주의] 함수 내에서 선언된 지역변수의 주소값, 포인터를 반환하는 것에 주의해야한다. 지역변수는 자동저장기간을 가지기 때문에, 함수 실행 종료와 동시에 할당된 메모리가 사라진다. 즉 지역변수의 주소값이 없어진다. 그러므로 static, 전역변수 등을 잘 활용해 주어야 한다.