2017-10-09 13:51:14

C/C++에서 포인터를 이해하는 것은 상당히 중요한 부분을 차지한다. 사실 어렵다는 인식이 강해서 그렇지 사실 그렇게 어렵지 않은 문법이다. 가장 큰 적은 우리 안에 있는 두려움! 표현이 살짝 오글거리지만, 아무튼 용기 있게 포인터 공부를 시작해보자. 



▶ 포인터에 대한 직관적 이해


포인터를 이해하기 위해 간단한 비유를 만들어보았다.


그림1. 사랑아파트 3동에 3명의 친구들이 살고 있다.



행복초등학교에 다니고 있는 철수, 영희, 바둑이는 모두 다 사랑아파트 3동에 거주하고 있다. 철수는 504호, 영희는 402호, 바둑이는 201호에 각각 살고 있다. 어느날 이들과 같은 반 친구인 영수가 놀러왔다. 영수는 친구들을 집 밖으로 불러내기 위해 두 가지 방법을 사용할 수 있다. 아파트 앞에서 "철수야 나와!"라고 할 수도 있고, 약간 장난스럽게 주소를 이용해서 "504호에 살고 있는 놈 나와!"라고 외칠 수도 있다. 


C/C++적으로 이 상황을 나타내보자. 철수를 직접 부르는 것은 변수에 대입되어 있는 값을 이용하는 것이고, 주소를 이용해서 부르는 것은 포인터(pointer)를 이용하는 것이다. 포인터는 말 그대로 가리키는 것이다. 주소는 어딘가를 찾아갈 수 있게 해주는 것이므로, 역시 어딘가를 가리키고 있다고 말할 수 있다. 


우리가 변수를 선언하고 그 변수에 어떤 숫자나 문자를 대입하면, 컴퓨터 어딘가의 메모리를 사용한 것이다. 그 메모리의 주소 값을 저장하기 위한 변수가 바로 포인터이다. 그러니까 변수에 대입한 값이 메모리 내에서 어디에 있는지 알려면, 그 주소를 알아야하는데 그 주소와 관련된 문법이 포인터다. 이해를 위해 하나의 예제를 실행해보자.



▶ 포인터에 대한 간단한 예제


pointer.cpp

#include <iostream>


using namespace std;


int main(void)

{

int a = 10; // 변수 a 선언 및 대입

double b = 3.14; // 변수 b 선언 및 대입

int* pA = &a; // 포인터 변수 pA 선언 및 대입 

double* pB = &b; // 포인터 변수 pB 선언 및 대입


cout<<a<<endl;     // 변수 a 값

cout<<b<<endl;     // 변수 b 값

cout<<pA<<endl;     // 변수 a의 주소값

cout<<pB<<endl;     // 변수 b의 주소값

cout<<*pA<<endl;   // 변수 a의 주소값(pA)이 가리키고 있는 값, 즉 변수 a 값

cout<<*pB<<endl;   // 변수 b의 주소값(pB)이 가리키고 있는 값, 즉 변수 b 값


return 0;

}


실행결과를 보기 전에 일단 코드를 한줄 한줄 충분히 이해하자. 우선 int형 변수 a를 선언한 후 10을 대입했고, double형 변수 b에 3.14를 대입했다. 여기까지는 간단하다. 그 다음에 포인터 변수 pA와 pB를 선언해줬는데, 보다시피 *연산자를 활용해야 한다. 


int* pA = &a; // 포인터 변수 pA 선언 및 변수a의 주소값 대입 

double* pB = &b; // 포인터 변수 pB 선언 및 변수b의 주소값 대입


선언하는 변수가 일반적인 변수가 아니라 포인터 변수라는 것을 알려주는 것이다. 이때 int*, double*처럼 포인터 변수가 가리키고 있는 변수와 동일한 타입을 사용해줘야한다. a는 int형 변수이므로 a를 가리키고 있는 포인터 변수 pA는 int*가 되야하는 것이다. 그리고 변수a와 변수b의 주소값을 대입할 때는 &연산자(어드레스 연산자)를 사용한다.  


이제 변수 a, b에 대입되어 있는 값들을 모니터에 출력해볼 것이다. 이것을 직접 접근(Direct Access)이라고 부른다. 그리고 변수 a, b의 주소값을 확인해볼 것이다. 마지막으로는 변수 a, b의 주소값을 이용해서 변수 a, b에 대입되어 있는 값들을 출력할 것이다. 이것은 간접 접근(Indirect Access)이라고 불린다. 


cout<<a<<endl;     // 변수 a 값

cout<<b<<endl;     // 변수 b 값

cout<<pA<<endl;     // 변수 a의 주소값

cout<<pB<<endl;     // 변수 b의 주소값

cout<<*pA<<endl;   // 변수 a의 주소값(pA)이 가리키고 있는 값, 즉 변수 a 값

cout<<*pB<<endl;   // 변수 b의 주소값(pB)이 가리키고 있는 값, 즉 변수 b 값


포인터 변수들을 이용해서 변수 a, b를 참조할 때(간접 접근)에도 *연산자를 활용한다는 것을 주목하자. pointer.cpp를 실행하면 아래와 같이 출력될 것을 기대할 수 있다. 


10

3.14

변수 a의 주소값

변수 b의 주소값

10

3.14


코드를 제대로 이해했다면, 이제 실행결과를 확인해보자.


그림2. pointer.cpp 실행결과

 

우리의 예상대로 출력되었다. 3, 4번째 줄에서 확인할 수 있는 것은 주소값이 16진수로 표현되어 있다는 점이다. 



▶ 포인터와 함수


이번에는 함수를 선언하고 호출할 때 포인터 문법이 어떻게 사용되는지 살펴보겠다. 간단한 덧셈 함수를 만들건데, 하나는 직접 접근을 이용해서, 다른 하나는 간접 접근을 이용할 것이다. 소스코드는 아래와 같다.


pointer_function.cpp


# include <iostream>


using namespace std;


int Add1(int a, int b) // 덧셈함수 1 선언

{

return a + b;

}


int Add2(int* pA, int* pB) // 덧셈함수 2 선언

{

return *pA + *pB;

}


int main(void)

{

int num1 = 5;

int num2 = 7;

int* pNum1 = &num1;

int* pNum2 = &num2;


cout<<Add1(num1, num2)<<endl;

cout<<Add2(pNum1, pNum2)<<endl;


return 0;


}


main함수부터 코드를 읽어보자. 먼저 num1, num2라는 이름의 int 변수들을 선언해주고 각각 5, 7을 대입했다. 그리고 pNum1, pNum2라는 포인터 변수를 선언한 다음에 각각 변수 num1과 num2를 가리키게 했다. 그리고 Add1이라는 함수에 num1, num2를 입력변수로 넣어준 것을 모니터에 출력하게 만들었다. 


cout<<Add1(num1, num2)<<endl;


Add1함수를 위에서 보면 간단히 입력받은 두 정수를 더해서 반환해준다는 것을 알 수 있다. 그 다음 줄에서는 Add2함수에 두 개의 포인터변수를 입력변수로 넣어준 결과를 모니터에 출력하라고 명령하고 있다. 다른 말로 하면, 변수 num1, num2의 주소값들을 Add2함수에 입력해서 나온 출력값을 모니터에 보여주라는 것이다.   


cout<<Add2(pNum1, pNum2)<<endl;


Add2함수를 자세히 살펴보자. 


int Add2(int* pA, int* pB) // 덧셈함수 2 선언

{

return *pA + *pB;

}


입력변수 자리에 포인터 변수가 들어온다는 것을 알리기 위해 함수의 선언부분에 int* pA, int* pB라고 코딩했고, 포인터가 가리키는 변수를 참조해서 더하기 위해 *연산자를 이용해서 *pA + *pB라고 코딩했다. 코드를 실행하면 아래와 같이 출력된다. 


그림3. pointer_function.cpp 실행결과



▶ 정리


오늘 간단히 포인터에 대해서 정리해봤다. 포인터는 변수 자체가 아니라 변수의 주소를 가지고 논다는 것을 기억하면 될 것 같다. 


이 포스팅이 누군가에게 도움이 되길 소망하며 글을 마친다. 질문이 있거나, 제가 이해한 것이 잘못된 것 있다면 댓글 남겨주세요!