이 장에서는 포인터에 대해 본격적으로 다루며 다음 장은 배열과 함께 포인터를 같이 다룬다. 앞에서도 이미 포인터에 대해서 기본적인 개념을 다룬 바 있는데 3장에서 포인터 타입에 대해 간단하게 소개를 했으며 6장에서 포인터를 이용한 참조 호출에 대해서도 연구해 보았다. 만약 이 두 내용이 잘 기억이 나지 않거나 아직도 혼란스럽다면 복습을 먼저 하고 오기 바란다.

C언어 공부를 시작할 때부터 흉흉한 소문을 들어서 잘 알고 있겠지만 포인터는 C의 모든 주제 중에서 가장 어려운 과목이다. 중급 정도의 개발자들도 항상 포인터가 어렵다고 하는걸로 봐서 과연 쉽지 않은 난적임은 분명한 것 같다. 그래서 여러분들은 포인터에 대한 모든 것을 한 번에 다 알겠다고 덤벼들어서는 안된다. 이론적으로 집중 연구한다고 해서 한 번에 다 이해될 수 있는 것은 아니며 실제 프로젝트에서 포인터가 반드시 필요한 상황을 만나 봐야 그제서야 포인터가 무엇인지 어렴풋이 실감이 날 것이다.

처음부터 욕심 내지 말고 기본 개념을 먼저 익히되 모르는 부분은 일단 이해되는데까지만 접수하고 프로젝트와 습작을 만들면서 또는 다양한 예제를 보면서 그때 그때 필요한 부분을 찾아서 다시 복습하는 전략을 쓰는 것이 좋다. 극단적으로 말해 scanf로 정수형을 입력받을 때 &가 필요하다는 것과 배열명이 포인터 상수라는 것 정도만 알아도 일단 프로그램을 짤 수는 있다. 제발 포인터가 잘 이해되지 않는다하여 일치감치 C를 포기하는 우를 범하지 않기를 바라는 마음이다.

10-1.포인터 연산

10-1-가.T형 포인터

3장에서 정의했듯이 포인터는 메모리의 한 지점, 간단히 말해 번지값을 가지는 변수이다. 어떠한 형태의 변수든지(register형만 제외하고) 반드시 메모리에 보관되며 모든 메모리는 번지를 가지고 있다. 따라서 이 변수의 번지를 가리키는 포인터 변수를 항상 선언할 수 있다. 문장화해서 정리해 보도록 하자.

 

임의의 타입 T 있을 T형의 포인터 변수를 선언할 있다.

 

int, char, double 등의 기본적인 데이터 타입에 대해 int *, char *, double *형의 변수를 선언할 수 있음은 물론이고 구조체, 공용체, 배열에 대해서도 포인터형을 만들 수 있다. 사용자가 직접 만든 타입에 대해서도 포인터형 변수를 선언할 수 있으며 심지어는 포인터 타입에 대해서도 포인터를 선언할 수 있다. 다음 각 * 기호는 프로그램 구성 요소 7가지 중 어떤 것인지 구분해 보자.

 

i=3*4;

printf("%d",*pi);

int *pi;

 

첫 번째 *는 두말할 필요도 없이 곱하기 연산자이며 상수 3과 4를 곱한다. 두 번째 *는 포인터 변수가 가리키는 번지의 내용을 읽어내는 포인터 연산자이다. 곱하기 연산자와 포인터 연산자는 모양은 같지만 취하는 피연산자 개수가 다르므로 비교적 쉽게 구분할 수 있다. 곱하기 연산자는 이항 연산자이고 포인터 연산자는 단항 연산자인데 이처럼 C에는 모양은 같지만 피연산자 개수가 다른 연산자가 몇 가지 더 있다(&, -등).

세 번째 *도 곱하기나 포인터 연산자와 모양은 같지만 이것은 연산자가 아니다. int와 pi를 곱하라는 뜻도 아니며 지금 막 정의하려고 하는 pi 변수가 가리키는 번지의 내용을 읽어라는 포인터 연산자도 아니다. 여기서 사용된 *기호는 포인터를 선언할 때 사용하는 구두점이다. T형 변수는 다음과 같이 선언한다.

 

T v;

 

타입 이름 T다음에 원하는 변수명을 적으면 이 변수는 T형의 변수가 되는데 int i; 선언문이 전형적이다. 이 선언문의 변수명 앞에 * 구두점을 붙여서 T *v;로 선언하면 이 변수는 T형의 포인터 변수가 된다. T형 변수를 선언할 수 있으면 T *형은 항상 선언 가능하므로 T가 어떤 타입이든지 상관없이 변수 이름 앞에 *구두점만 붙이면 T형 포인터 변수를 선언할 수 있다. * 구두점은 다음 두 가지 형식으로 표기할 수 있다.

 

int *pi;

int* pi;

 

타입명 int 다음에 한칸 띄우고 *와 변수명을 쓸 수도 있고 int 다음에 바로 붙여서 *를 쓰고 한칸 띄운 후 변수명을 쓸 수도 있다. C언어는 프리포맷을 지원하므로 공백이 어디에 있는가는 중요하지 않다. 그래서 int*pi나 int * pi와 같이 공백을 마음대로 써도 상관없지만 주로 위 두 가지 형식 중 하나가 사용된다. int*를 하나의 타입으로 볼 때는 ②번 형식을 주로 사용하며 변수가 포인터형임을 강조할 때는 ①번 형식을 사용한다. 한 행에 두 개의 변수를 선언하는 다음 선언문들을 보자.

 

int *i,j;                   // i는 포인터, j는 정수형

int* i,j;                   // i는 포인터, j는 정수형

int *i,*j;                  // i와 j 모두 포인터

 

위의 두 경우는 i를 정수형 포인터 변수로, j는 정수형 변수로 선언하는데 보다시피 int*라고 붙여 쓴다고 해서 컴파일러가 int*를 하나의 타입으로 인정하는 것은 아니다. 문법은 포인터형으로 선언하고 싶은 변수명앞에 일일이 * 구두점을 붙이도록 규정하고 있다. 아래쪽 선언문은 i와 j가 모두 정수형 포인터로 선언된다. 만약 int*가 한 묶음으로 포인터 타입을 의미한다면 정수형 변수와 정수형 포인터 변수를 한 번에 선언할 수 없을 것이다.

공백을 어디다 둘 것인가는 선언 내용에 영향을 주지 않으며 어디까지나 개인의 취향 문제일 뿐이므로 사실 중요하다고는 할 수 없지만 두 형식을 섞어서 사용하는 것은 바람직하지 않다. 나는 개인적으로 ①번 형식을 선호하는 편이며 ②번 형식만 고집하는 사람도 있되 자신만의 기준을 정해 일관되게 지키기만 하면 될 것 같다. 참고로 C 스팩 문서는 모두 ①번 형식으로 되어 있고 C++ 스팩 문서에는 두 형식이 혼재하되 ②번이 좀 더 우세하다. 비주얼 C++의 라이브러리인 MFC는 모두 ②번 형식으로 되어 있다.