본문 바로가기
컴퓨터/C++_STL

포인터에 대한 짤막한 이야기

by adnoctum 2014. 4. 2.




   모든 변수는 주소를 갖는다. 메모리에 생성된 모든 변수는 그 메모리 상에서의 위치, 즉 주소를 갖는다. 이것은 그 변수의 형(type)에 상관없이 해당되는 말이다. 그 변수가 포인터형 변수이든 아니든 이 말은 적용이 된다. 아마도 포인터를 익숙하게 사용하기 힘든 많은 이유 중의 하나가 바로 이 사실을 잊기 때문이 아닌가 생각된다. 위 사실을 상기하면서 얘기를 계속 해 나가보자. 


   코드에 사용된 대부분의 변수, 메모리에 존재하게 된 변수는 그 변수가 저장될 위치, 즉 주소가 필요하고, 따라서 그 변수의 형(type)에 상관없이 주소를 갖고 있다. 그리고 그 주소에 어떤 값이 들어 가는데, 그 값이 그 변수의 값이 된다. 포인터형 변수는 단지 그 '값'의 위치에 '주소'가 들어 가 있는 것 뿐이다. 물론 포인터형 변수도 '변수'이기 때문에 '주소'를 갖는다. 그림으로 보자면 다음과 같다. 





위는 다음과 같은 상황이다. 


double left = 23.48; 

double* param = &left; 


즉, pointer 형 변수 param 자체도 주소를 갖고 있다. left 도 주소를 갖고 있다. left의 '값'은 23.48 이고, param 의 '값'은 left 의 주소, 0x0202 가 들어 있다. 즉, pointer 형 변수는 그 변수 자체도 주소를 갖고 있으며, 그 값에 '주소'가 들어 가는 것이다. 포인터형 변수의 '값'에 들어 있는 주소로 가서 그 값을 읽어 와야 할 경우 *param 을 이용한다. 그 변수가 무엇이든 그 변수가 저장된 '주소'를 갖고 오기 위해선 & 연산자를 사용한다. param의 주소인 0x0011 을 갖고 오기 위해서는 &param 을 하면 되는 것이다. 변수 자체가 primitive type 이 아닌, 구조체나 class 일 경우 (*param).width, 처럼 할 수 있는 것에 대한 간단 표기법으로 param->width 를 사용할 수 있다. 즉, -> 는 (*address).member 에 대한 short-cut에 불과하다. 변수가 포인터형이라는 것은 그 '값'에 주소가 들어 간다는 뜻이고, double* 과 같이 특정 type 의 포인터 형 변수라는 것은 그 '값'에 있는 주소로 가서 값을 읽되 double 형으로 인식을 하라는 것이다. 0x0202 주소로 가면 bit 로 보자면 0100101000101110101010100~~과 같이 어떤 값이 있을 것이다. 만약 double 형이면 0x0202 의 주소에서 8byte(64bit)까지만 읽는데, 그 때 그 bit의 해석은 정해진 표준(IEEE 754-1985)에 의해 해석하라는 의미이다. 만약 unsigned char 였다면 1byte만 읽고 8bit 의 2진법을 10진법으로 변환해서 176 따위의 값으로 변환하였을 것이다(예를 들면). 즉, 메모리에는 항상 어떤 값이 이미 있을 뿐이고, 그 주소에 할당된 변수의 형(type)에 따라 그 값을 어떻게 해석하느냐만 바뀔 뿐이다. 


   또한 { } 이 기호는 단순히 lay-out 을 위한 기호가 아니다. { 이 시작되면서 stack 이 생성되고, } 으로 닫히면서 stack 에 만들어 졌던 지역 변수들이 메모리에서 사라 진다. 그래서 

{

    int a = 5; 

    std::cout << a << std::endl; 

}


{

   int a = 8; 

   std::cout << a << std::endl; 

}

과 같이 해도 이미 있는 변수를 사용했다는 오류가 발생하지 않는다. 왜냐 하면 위의 각 a 는 서로 다른 stack 공간에 존재하게 되기 때문이다. 함수를 정의할 때 사용된 { } 기호 역시 마찬가지여서 함수 내부, 즉 함수가 시작되면서 열린 { 에 의해 만들어진 stack 에 존재하던 변수들은 함수가 끝날 때 사용된 } 기호에 의해 stack 이 없어질 때 메모리에서 사라지게 된다. 만약 함수 내부에서 new 와 같이 동적으로 할당된 변수는 stack 이 아닌 공간에 할당되기 때문에 stack 이 없어져도 여전히 참조하여 사용할 수 있는 것이다. 또한, 함수의 인자로 '주소'를 받는다는 것은 함수가 열리면서 만들어 진 stack 이외의 공간에 존재하는 변수의 주소를 받은 것이기 때문에 그 주소로 가서 값을 변경시키면 함수가 끝나도 여전히 변경된 값으로 남아 있게 되는 것이다. 





   포인터 자체는 어려운 개념이 아니기 때문에 이해하기는 어렵지 않지만 사용하기는 항상 혼란을 불러 일으키는 듯 하다. 지금까지 C++ 을 알려 줄 때 포인터를 혼동 없이 처음부터 사용한 사람이 없었다(kaist 학부 출신 3명, 서울대 학부 출신 1명). * 의 의미는 변수와 * 의 위치에 따라 의미가 여럿 이고, & 역시 그렇기 때문에 * 를 사용해야 하는지 & 를 사용해야 하는지 고미없이 되기 까지는 상당한 시간이 필요한 듯 하다. 일단 단일 포인터만 이해를 하면 몇 중 포인터이든 쉽게 적응할 것 같은데 단일 포인터에 적응하기가 여간 만만치 않은 듯 하다. 결국... 코딩(프로그래밍)도 매우 경험적인 것이다. 머리 좋은 것이 물론 도움이 많이 되긴 하는데, 직접 해보지 않으면 백날 남의 코드 보고 이해하고 남의 소스 코드 베껴서 우여 곡절 끝에 만든다 해도 별로 늘지 않을 것이다. 우리가 음식을 먹고 맛이 있는지 없는지 판단하는 것과 직접 맛있는 음식을 할 수 있는 것은 전혀 다른 문제이듯 코딩도 마찬가지인 것이다. 




'컴퓨터 > C++_STL' 카테고리의 다른 글

bind1st 와 bind2nd  (2) 2014.12.17
포인터, 참조, 값 형식에 관한 개인적 관례  (0) 2014.05.09
파일 읽기 편하게 하기  (0) 2013.05.01
const pointer  (0) 2011.11.23
isnan과 isinf  (0) 2010.12.06