메모리(memory)와 주소(address)
어떤 변수를 선언(declare)하든, 시스템에게 해당 사이즈 만큼의 메모리를 요청할 것이다. 그리고 큰 문제가 없는한 메모리를 할당(allocate)받게 될 것이다. int 형으로 요청하면 4바이트를 요청할 것이고, 뭐 데이터형마다 각기 싸이즈를 요구할 것이다. 그리고 각각의 메모리는 주소를 갖는다. 즉, 데이터와 주소는 쌍을 이룬다. 그 주소를 알면 그 저장된 값도 언제든지 가져다가 쓸 수 있는 것이다.

포인터(pointer)
포인터는 주소값을 저장하는 녀석이다.

포인터에 저장된 주소값을 이용하면 원하는 대상이 저장되어있는 메모리를 직접 찾아갈수가 있다.


포인터의 이동단위
C에서는 포인터의 연산이 가능하다. 어떤 포인터가 어떤 주소값을 갖고 있는 상태에서 +1 해서 포인터의 값을 1 증가 시키면 포인터는 다음 주소의 값을 갖게 되는데, 이때 이동단위는 해당 포인터의 데이터형에 의존한다. 가령 char 형은 1바이트씩 이동하고, int 형은 4바이트씩 이동한다.

실제 메모리는 비트들로 되어있으므로, char 형은 8 비트씩 뛰어다니느 것이고, int형은 32비트씩 뛰게 되는 것이다.


주소연산자 (address-of operator) &
어떤 대상에 대해서, 저장된 값이 아닌, 메모리 주소(address)값을 반환한다. address-of 라고 읽기도 한다.


Reference (참조)
Reference 한다는 것은, 기본적으로, 어떠한 대상을 가리키는 것을 의미한다. C 에서는 어떠한 대상을 가리키는 방법으로 주소값을 사용한다. 주소값은 & 를 통해서 얻을수 있고, pointer variable 에 값으로 저장해두었다가 사용할 수도 있다. 참고로, C++ 에서는 주소를 직접 pointer variable 에 저장하지 않고도, 메모리에 저장된 대상을 직접 참조하는 참조변수(reference variable) 를 선언할 수 있지만, C 에는 그런게 없다. 따라서, C 의 context 에서는 reference 한다는 표현은 결국, 주소값 ( &를 통해 구한 값이든, pointer 에 저장된 값이든) 을 통해서 대상을 가리킨다는 말과 같다.


역참조 연산자 (dereference operator) *
어떠한 대상이 다른 대상의 주소를 가지고 있을때, 그 주소를 따라가서, 다른 대상의 값을 취하는 것을 dereference (역참조) 라고 한다.
역참조연산자는 * 를 사용한다.

그러므로, 주소연산과 역참조연산은 역연산 관계이다. 따라서, 이경우, & 와 * 는 약분된다.
예를 들어. 어떤 p 가 포인터라고 하자.  그러면, &*p 는 p 가 된다. 어떤 a 가 일반 변수라고 하자. 그러면 *&a 는 a 가 된다.

오프셋 역참조(dereference w/ offset) []
어떤 주소에서 부터 지정된 스텝을 오프셋시킨 상태에서 역참조하는 연산자이다.

즉,  arr[ i ]  와   * ( arr + i ) 가 같은 뜻이 된다.



포인터 변수 선언
포인터 변수의 선언 구문은 다음과 같다.
type  * variable1 , * variable2 , ... ;


* 연산자는 토큰으로, 독립적으로 쪼개질수 있으므로 붙여쓰던 띄어쓰던 그건 중요하지 않다. 다만 우선순위에서 밀릴때는 다른 연산자들과 마찬가지로, 결합우선순위를 확보하기위해 괄호를 쓰면된다. 참고로, * 연산자의 우선순위는 [] 에도 밀리고, 함수의 () 에도 밀린다.

예. int (*arr)[2];

int (*FnPick(int PickNum))(int, int) { ... }


매개변수로 포인터 변수를 넘길수 있고, 프로토타입의 경우 변수명이 필요없으므로 다음과 같은 형태가 된다.

return_type function_name ( type * , type * , ... ) ;

예. int func( int * , int* );



배열(array)명
배열명은 포인터 상수이다. 즉, char 형 배열을 선언했다면 그 배열명은 char형 포인터가 된다. 즉, 그 배열의 시작 주소를 값으로 갖고 있는 것이다. 보통의 포인터가 포인터변수(pointer variable) 임에 비해 배열명은 상수이다. 변경이 안된다. 즉, 포인터는 배정문을 통해 어디든 원하는 주소를 가리킬 수 있겠지만, 배열명에 배정문을 써서 값을 변경하는 것은 불가한다.

배열문이 포인터상수인것은 어찌보면 당연하다. 배열을 선언과 동시에, 배열명은 배열의 시작주소를 가리키는 일종의 고유명사화된 주소라고 할 수 있다.

배열의 인덱스(index)
배열의 인덱스를 나타내는 연산자 [ ] 는 사실은 오프셋 역참조 연산자이다. 배열명을 기점으로 몇칸이동(데이터형에 의존)한 지점을 역참조 할 것인가 를 나타낸다. 어떤 아파트가 있는데, 그 배열명이 그 아파트의 첫버째 집을 나타낸다면, 인덱스는 몇호에 사는 사람을 나타낸다. 즉, 호수까지 이동을 한 상태에서 역참조를 하는 것이다.

예.

char Apartment[100] = "Hi, everyone. How are you? I'm Fine, Thank you. And you? -_-" ;
 
printf( "%s" , &Apartment[15] ) ;

여기서, &Aprtment[15] 부분을 보자. Apartment 는 배열명이다. 이 아파트의 첫번째 번지수를 나타낸다. 즉, 첫번째 번지에는 H가 살고 있다. 즉, 저 H의 주소가 되는 것이다. 그런데 Apartment[15] 라고 했다. 15번째 다음의 집에는 누가 살고 있나를 말한다. 참고로 [0] 은 H가 되겠다. 0 만큼 다음 집이니까 첫집 그대로이다.

[0] : H
[1] : i
[2] : ,
[3] : 공백
[4] : e
[5] : v
...

[15] : o

즉, o 가 된다. 이때 이것은 주소가 아니라 이미 역참조( 해당주소에 살고있는 사람) 임을 상기하자.
따라서, 앞에 &를 붙여서, &Apartment[15] 라고 하게되면,  o 의 번지수를 가리키게 된다.

참고로. printf() 의 %s 는 char 형 주소를 만나면 반복적으로 주소를 다음으로 넘겨가면서 null 문자를 만날때까지 찍어댄다. 따라서 결과는 다음과 같다.

ow are you? I'm Fine, Thank you. And you? -_-


이것은  printf( "%s" , Apartment + 15 ) ;  와 같다.


포인터 오프셋(offset)

벡터 스타일의 데이터를 쓰고 싶을때, 보통의 습관은 인덱스를 1부터 쓰는것을 선호한다. 그러나 [] 연산자를 인덱스처럼 사용하게 되면, 인덱스가 0 부터 출발하기 때문에 우리의 습관과 일치하지 않는다.

이럴때 포인터를 앞으로 하나 땡겨주면, 뒤로 한칸 옮겼을때 요청한 메모리의 첫 칸을 사용하기 때문에 [1] 이 첫 주소가 된다.


따라서...
입력장치로 부터 양의정수 n을 받아서, 원하는 형태의 데이터 타입으로 n 개의 메모리를 요청하고, 첫번째 인덱스 1부터 n번째 인덱스 까지 사용하는 방법은 다음과 같다.

typedef double entry;

...

int n;
entry *a;

...   // get n somewhere

a = calloc( n , sizeof(entry) );
--a;


이런식으로 하면, a[1] 부터 a[n] 까지 쓸수있다.
대신 이 경우, a[0] 는, 우리가 요청한 곳도 아니고 접근해도 되는곳인지도 알수없으므로 절대로 써서는 안된다.
메모리를 반납할때도 free(a+1); 과 같은 식으로 반납해야 한다.