본문 바로가기

프로그래밍/C

포인터(Pointer)와 함수

swap함수는 대개 다음과 같이 만든다.

void swap(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
} 
이렇게 하는 이유는 포인터가 아닌 그냥 변수를 넣어줄 경우에 원하는 결과가 나오지 않기 때문이다.
이유인 즉, 함수를 호출할 때에 새로운 스택이 잡히게 되고 함수의 인자도 그 스택 안에 새로 할당된 값이다.
그런데 이때 변수 값을 넘겨주게 되면 함수에 있는 인자에 그 값이 들어가게 되는 것이다.

예를 들어
void swap(int a, int b) {
  int temp = a;
  a = b;
  b = temp;
} 
라는 함수를 만들고 main에서 i=2, j=3을 할당한 뒤 swap(i, j);를 호출하면 새로운 스택이 만들어 지는데, 이 스택의 a라는 변수에 i의 값인 2가 들어가고, b라는 변수에 j의 값인 3이 들어간다.

그리고 a와 b는 서로 값을 바꾸게 되는데, 함수를 빠져나가는 순간 스택 또한 사라진다.
그래서 스택에 있던 a, b라는 변수도 사라지게 된다. 결국 swap(i, j)를 하고 나도 i와 j 값은 전혀 변화가 없는 것이다.
하지만 포인터를 이용한 swap함수는 호출을 할 때 swap(&i, &j)와 같이 변수의 주소를 넘긴다.

그렇게 되면 함수가 호출됐을 때 스택이 만들어지고 그 스택의 두개의 변수인 a, b는 각각 i와 j에 대한 주소를 가지고 있는다. *a, *b는 각각 i와 j의 주소에 있는 값을 의미하므로  이 부분을 변화시키면 당연히 i와 j의 값이 바뀌게 된다.


그렇다면 이중 포인터의 경우를 살펴보자.
어떠한 특별한 경우에 함수의 인자로 이중 포인터를 넘겨줘야 하는 상황이 발생한다.

Shape이라는 클래스가 있다고 하자.
Shape *i, *j; 가 메인에 있다.
이때 i에는 아무런 객체가 들어있지 않고, j에는 특정한 객체가 들어있다고 하자.
i에 j의 객체를 복사한 새로운 객체를 만들어주는 void func(Shape* a, Shape *b)라는 함수를 만들었다고 하자.
이 함수는 아마도 다음과 같이 구성될 것이다.
void func(Shape* a, Shape* b) {
  a = new Shape(*b);
} 


호출 -> func(i, j);

그런데 이렇게 하면 불행히도 우리가 원하는 결과를 얻을 수 없다.
다시 한번 곰곰히 생각해 보면, new Shape(*b)를 하게 되면 Shape 객체에 대한 주소값이 반환된다.
이 주소값을 담을 수 있는 곳은 당연히 Shape*가 된다.

그런데 우리는 이러한 작업이 수행되는 곳이 함수라는 것을 기억해야 한다.
함수란 무엇인가?? 스택이다!! 스택은 들어가는 순간 매개변수가 스택의 변수가 되고, 빠져 나가는 순간 스택이 사라지면서 매개변수 또한 사라진다.

그렇다면 함수가 호출될 때 넘겨지는 값이 무엇인가?
a에는 i를 넘겨준다. i는 Shape* 형이고 아무 곳도 가리키지 않는 무의미한 포인터이다.(자칫 위험할 수도 있는).
그러면 a는 i가 가리키고 있었던 어떤 주소가 될 것이다.(어느 곳인지는 아무도 알 수 없다.)
그리고 b에는 j를 넘겨주는데, j는 Shape객체의 주소이므로 b에는 그 객체에 대한 주소가 넘어간다.
그래서 new Shape(*b)하면 b와 똑같은 객체를 하나 더 생성할 것이다. 그리고 그 주소를 a에게 넘겨준다. 그런데 a는 아무도 모르는 어떤 주소이고 이곳에 새로운 객체를 넣는다는 것은 아주 위험한 행동이다. (결과는 아무도 예측할 수 없다.)
그래서 함수를 새롭게 고쳐야 한다.
void func(Shape** a, Shape* b) {
  *a = new Shape(*b);
} 

호출-> func(&i, j);

a를 보면 Shape** 형이고, 이것이 의미하는 바는 Shape자료형에 대한 주소값을 가지고 있는 변수의 주소값을 가지는 변수라는 뜻이다-_-. 쉽지는 않지만 좀 더 간단하게 말하자면 포인터에 대한 포인터이다-_-;;
앞의 swap함수에서 함수에는 포인터를 사용하고, 함수 호출시에는 변수의 주소값을 넘겨준 것을 생각하면 조금 이해할 수도 있겠다.
*a는 Shape객체에 대한 포인터이므로 new Shape(*b)를 받을 수 있다.
그리고 함수를 호출하는 부분에서 &i를 하게 되면 i라는 변수의 주소를 주게 된다.
즉, 앞에서와의 차이점을 얘기하자면 앞에서는 i가 가리키는 곳의 주소를 전달했는데에 반해 여기에서는 i 자신의 주소를 전달한 것이다.
이 부분이 엄청나게 중요한 부분이다. 포인터라 하더라도 일종의 변수이다.
즉, 자신만의 공간을 가진다는 말이다. int * a라고 하면 a는 int형의 주소를 가리키는 변수이지만 &a는 그러한 자신의 주소가 된다는 것이다.

어쨌든 이렇게 함으로써 할당도 되지 않는 모르는 주소에 새로운 객체를 넣는 행위를 하지 않게 된다. 이해하기 쉽지 않겠지만, 이 부분은 함수를 쓰지 않았을 때 a = new Shape(*b);와 같다.(지극히 정상적이다.)
반면 앞의 이중 포인터를 쓰지 않은 경우는 *a = new Shape(*b);와 같다.(이 경우에는 a가 가리키는 곳의 값에다가 새로운 객체를 넣는 이상한 행동을 하게 된다. 결국 비정상 종료를 하게 될 것이다.)

'프로그래밍 > C' 카테고리의 다른 글

memset() 함수  (0) 2010.01.04