이중 포인터를 사용하는건 주로 포인터의 동적 배열을 나타내기 위해서 이다.
우선 아래와 같은 클래스가 하나 있다고 가정 해보자.

class CTest
{
public:
 CTest() {}
 int a;
};

이녀석에 대한 포인터를 선언하고 객체 하나를 생성하려면 어떻게 해야할까?
다음과 같이 써주면 된다.

CTest *pTest = new CTest;

이렇게 하면 pTest는 CTest의 객체를 참조한다.

근데 이제 CTest객체가 여러개 필요한 상황이 나오면 어떻게 해야할까
처음에 아무 생각없이 아래와 같이 했었다.
CTest *pTest1 = new CTest;
CTest *pTest2 = new CTest;
...

필요한게 정해져있다면 뭐 저래도 괜찮겠지만.. 그래도 매직넘버를 늘리는건 좋지 않다..
그래서 좋은 방법중 하나는 배열로 생성하는 방법이다.

배열로의 생성은 아래와 같이 한다.
CTest aTest[3];

가장 무난하다.
그리고 이와 대응 되도록 포인터를 3개 가진 변수를 선언하려면 다음과 같이 한다.
 CTest *apTest[3]; //pointer의 array이기 때문에 ap라고 지었다.
 apTest[0] = new CTest;
 apTest[1] = new CTest;
 apTest[2] = new CTest;

 apTest[0]->a = 1;
 apTest[1]->a = 2;
 2[apTest]->a = 3; //이런식으로 써도 상관없다 ㅋㅋㅋ
 printf("%d %d %d",apTest[0]->a, apTest[1]->a, apTest[2]->a);
 delete apTest[0];
 delete apTest[1];
 delete apTest[2];
//힙에 객체가 할당 되었으므로 할당된 갯수만큼 지워줘야함을 잊지 말 것
위의 것은 apTest[0]과 apTest[1]과 apTest[2] 가 들어갈 수 있는 배열 공간 *apTest[3]을 선언을 해놓고, 각각에다가 객체를 참조시켜준 것이다. 즉 apTest[0]은 CTest의 객체를 참조한다.

다른 방법으로 아래와 같은 방법도 있다.
 CTest *paTest = new CTest[10]; //포인터를 이용한 어레이라서 pa라고 붙엿다.
 paTest[0].a = 1;
 (paTest+1)->a = 2; //우왕 이런식으로도된다.
 paTest[125].a = 142;
 
 printf("%d %d %d",paTest[0].a, 1[paTest].a, paTest[125].a);
 
 delete[] paTest;

희한하지 않은가? 위는 동적으로 생성한 변수이다. 위처럼 동적으로 생성한 경우에는 런타임 시점에 크기를 계산 할 수가 없다.
따라서 현재 125번째 자리에도 자리 잡아놓았는데 저렇게 해도 잘 된다! 왜냐면 인덱스가 몇까지인지 모르니까~
그래서 sizeof(paTest)를 하면 겨우 4가 나올 뿐이다.(32bit cpu기준) 근데 절대로 할당하지 않은 메모리를 참조하는 바보같은 짓은 하지 말자 -_-;; 어짜피 컴파일 시점에서 aTest[125]는 *(paTest+125)로 바뀐다.(이에 대한 재밌는 얘기를 글의 후반에 작성하겠음)

위의 구문을 보면
CTest *paTest = new CTest[10]; 으로 해놨기 때문에 힙에 할당하는 값은 sizeof(CTest*) * 10 이다. 따라서 paTest에 CTest에 대한 배열 10개가 생겼다! 라고 생각하면 된다.(그래도 sizeof하면 sizeof(CTest*)값 밖에 안나온다. 위에서 말했듯이 동적이라서!)

음? CTest *paTest = new CTest[];나 CTest *paTest = new CTest; 로 했어도 paTest[133].a = 3; 이런식으로 선언한 영역을 넘어서는 참조가 가능하다????? (일부 컴파일러에서 가능)
만약 CTest *paTest = new CTest[]; 로 했다면 힙에 할당하는 값은 sizeof(CTest*) 뿐이며 참조는 머나먼곳까지 가능하다. 하지만 이 값은 보장될 수 없는 메모리에 쓰여지기 때문에 매우 위험하다. 만약 이런식으로 선언을 했다해도 이것은 배열이 만들어진게 아니다... 그리고 paTest[30].a 뭐 이런식으로 사용한다고해도.. 이건 절대로 배열이 아니다. 걍 (paTest+30)->a 일 뿐이다. 그냥 어딘지 모르는 번지를 참조하고 있을 뿐이다. 그래서 위와 같이 했을 경우 변수 참조가 아닌 함수 호출을 시도했을 경우 런타임 에러가 나면서 종료된다. 근데 버그인지 뭔지 모르겠지만 CTest *paTest = new CTest[10]; 이런식으로 선언했을때는 paTest[1000].func(); 이런식으로 함수 호출을 했을때 호출이 된다 -_-;; 짱.. C++ 젠장..

그러니까 이런 방법 -'[]로 동적 배열 잡는 멍청한 짓'은 하지 말자 -_-;;

아 그리고 CTest* 임에도 불구하고 이름을 pTest가 아닌 paTest로 잡은 이유는 우리가 이것을 배열처럼 사용할 것이기 때문이다.
참고로 CTest *pTest = new CTest; 일 때
pTest->a와 pTest[0].a 는 동치라는 점을 명심하자.
(pTest+2)->a와 pTest[2].a 역시 동치이다. 물론 그냥 쓰면 사용 안되고, 객체를 할당해 놓고 사용해야겠다. 그럴때 pa라고 붙여서 쓰자!!

CTest *paTest.. 이렇게 *이 붙어서 꼭 포인터 처럼 보이지만 p를 붙여서 컨벤션을 쓸꺼면 보통 객체 하나만 가리킬때 쓰기 때문에 헷깔리지 않기 위하여 pa를 붙이기로 했다. 즉 paTest는 배열의 첫번째 값을 가리킬 뿐이다. 우리는 실제로 paTest[0]...paTest[2]... 이런식으로 사용 할 것이기 때문에 pa를 붙이는게 더 낫다.
즉 저 명령은 그냥, aTest의 번지에 new CTest[10]; CTest의 객체를 참조하기 위한 공간 10개를 할당 하는 것이다.

다음 것도 보자.
 CTest **ppTest; //이놈이 바로 포인터의 포인터
 ppTest = new CTest*[3]; //포인터를 저장할 공간을 할당해야한다. 뭔가 아이러니하다.
 ppTest[0] = new CTest; //ppTest[0]을 pTest 하나라고 생각하면 이해가 쉬울 것이다.
 ppTest[1] = new CTest;
 ppTest[2] = new CTest;
 ppTest[0]->a = 3;

 delete ppTest[0];
 delete ppTest[1];
 delete ppTest[2];
 delete[] ppTest; //new가 4개니까 이렇게 해준다.

이해가 가는가?
포인터의 포인터이기 때문에 포인터를 위한 공간도 할당해야한다. ppTest = new CTest*[3]; 바로 이런식으로!
ppTest = new CTest*[3]; 이 구문은 CTest* 이라는 자료형을 3개 동적으로 생성하겠다는 말이다.
그러니까, ppTest는 CTest*을 3개 갖게 된다.
이제 위에서 만든 CTest*에 직접적인 CTest 객체를 할당할 차례다. 그것은 다음과 같이
ppTest[0] = new CTest; 해준다.
ppTest[0]이 바로 CTest* 인 것이다.

마지막으로 하나 더 굉장한 것을 보자. 머리가 아프다면 이 이상은 보지 않기를 권장함 -.-;;
 CTest **ppaTest;
 ppaTest = new CTest*[3];
 ppaTest[0] = new CTest[5];
 ppaTest[1] = new CTest[3];
 ppaTest[2] = new CTest[4];
 (ppaTest[2]+3)->a = 3;
 ppaTest[1][2].a = 4;
 ppaTest[0]->a = 1;
 printf("%d %d %d",ppaTest[0][0].a, ppaTest[1][2].a, ppaTest[2][3].a);
 
 delete[] ppaTest[0];
 delete[] ppaTest[1];
 delete[] ppaTest[2];
 delete[] ppaTest;

아아.. 이제 머리가 아프다..
차근 차근 봐보자
CTest **ppaTest;가 있다. CTest의 이중 포인터이다.
ppaTest = new CTest*[3] 을 해서 CTest*가 들어갈 공간 3개를 할당 하였다.
다음을 보면..
ppaTest[0] = new CTest[5];
즉.. ppaTest[0]은 자료형이 CTest* 인데, 이 자리에 CTest를 5개 동적으로 할당한 것이다.
즉 ppaTest[0]은
ppaTest[0][0], ppaTest[0][1], ppaTest[0][2], ppaTest[0][3], ppaTest[0][4] 이렇게 4개의 영역을 갖게 된다..
다른놈들도 마찬가지.....



아 머리가 깨진다.....
그래서 오늘의 결론은..


....

걍 벡터 쓰자 -_-;;




자 이제 위에서 말한 재밌는 이야기를 해주겠다.

위에서 보면
pTest[10]을 10[pTest] 처럼 쓴 부분을 볼 수 있다. 어째서 이것이 가능한가?
pTest[x]는 결국 컴파일러에 의해서 *(pTest + x)가 된다.
마찬가지로 x[pTest]는 컴파일러에 의해서 *(x + pTest)가 된다.
즉 pTest[x]와 x[pTest]가 똑같기 위한 조건은 덧셈(+)에 대해서 교환법칙이 성립한다는 것이다.
pTest[10]을 10[pTest]처러 쓸 수 있는 이유!!!!!! -> 덧셈에 대해서 교환 법칙이 성립하기 때문에.

그럼 이만~~~~~~~~~~~~~




p.s> kering님 감사
저작자 표시 비영리 변경 금지
신고
블로그 이미지

roter

JHB / Peripheral Programmer

댓글을 달아 주세요