Software Engineer라면, Pointer와 배열을 잘해야 한다는 건 자명한 사실인가요? 음.. 자명하기도 하고, 잘 알아야 Software도 잘 짤 수 있고, Debugging도 잘 할 수 있고, 뭐 그런 무기인 셈이죠. 다들 Pointer를 잘해야 한다고 주장을 하는데, Pointer를 쓰는 이유도 모르겠고. Pointer 잘 쓰면 C 잘하는 건가요? 그것도 아니고. 아.. 참.. 난감합니다.
누구에게 물어보기도 그렇고, 잘 알자고, Pointer책을 또 들여다 보기 그렇고. 하는 마음을 제가 십분 이해 합니다. 그렇다고 Pointer 책을 들입다 판다고 해서 Pointer의 대가가 되느냐. 그것도 아니고.
자, 그러면 Pointer에 대해서 제대로 짧고 굵게 한번 파보시는 건 어떠시렵니까? 이번 기회에 Pointer 잡고 넘어가시죠. 쉬어 가는 페이지였으면 하는 작은 바램이 있네요.
Pointer는 주소를 가리키는 word 크기의 자료 형입니다. 어렵죠. word형이라는 건 32bit machine에서는 32bit 크기를 갖고요, 64bit machine에서는 64bit크기를 갖습니다.
자 보세요. 32 bit machine에서 따져 볼게요.
int *address;
라고 선언하면 address는 pointer형 자료이고요. address 자체의 크기는 32bit 크기고요, address가 가리키는 자료 형은 integer type인 겝니다. 여기서 헷갈리기 쉬운 게 선언할 때 인데요. 실은 int* address 라고 선언을 하는 게 맞는 거에요. int*는 integer type을 가리키는 pointer형이라는 자료 구조이고요, address가 그 자료의 이름인 게죠.
그러니까, sizeof(address) = 32 bit, 4byte 이고요, address는 integer형을 가리키게 됩니다. 여기에 또하나 막상 data를 다룰 때* 연산자가 붙느냐 또는 안 붙는냐로 나눌 수 있는데, address는 주소 값을 갖고요, *address는 address가 갖고 있는 주소에 int형 만큼의 data를 가리킨다고 보시면 됩니다.
여기에서 & 연산자가 또 나오는데요, 이 녀석은 data의 실제 메모리 주소는 어디인가를 알아보는 연산자 인데요, 예를 들면 int data; 라고 선언한 후, &data를 출력해 보면 data가 어디에 저장되어 있는지 알 수 있어요. 요놈이 필요한 이유는 &를 이용해서 pointer에 data가 저장되어 있는 주소를 알려줄 수 있으니까 꼭 필요하죠.
본격적으로 Pointer에 대한 예를 들어 볼까요?
char *text = "Recipes";
라고 선언하고 나서, 막상 Data를 다룰 때, text는 Memory 어딘가에 Recipes라는 것이 저장되어 있을 텐데, 그 공간의 시작 부분 즉, R이 저장되어 있는 주소를 가리키게 되는 거지요. *text는 Recipes 문자열의 가장 첫 번째 문자열 "R"을 의미하는 거에요. 연산을 좀 해볼까요? text가 0x100이라고 했을 때 연산 좀 해보시죠. *(text++) 하면 얼마? = 0x101을 가리켜요. 왜냐고요? text가 char형 주소를 가리키기 때문에 값이 증가할 때는 1 byte씩 증가하죠.
Pointer 하면은 또 Array도 같이 딸려 나오는데, 그것도 참... 이상합니다. 사실 Pointer와 배열은 소녀시대와 원더걸스의 관계라고 하면 좀 이상 하려 나요. Pointer와 Array는 사실 같은 녀석들이라고 한다면 비밀누설죄에 걸려 들지 모르겠습니다.
일단은 Pointer와 Array의 관계를 좀 살펴 보시죠. Pointer와 Array하면은 *와 []가 나오겠죠. []과 *은 엄연히 다른 거지만, compiler는 같은 걸로 해석해서 compile해준답니다.
char *text = "Recipes";
text[1]; // 'e'
*(text+ 1); // 'e'
을 나타냅니다. 어라? Pointer로 선언했는데, text[1]이나, *(text+1)이나 같은 값을 가리키네요? 그건 compiler가 똑같이 해석하기 때문입니다. 이런걸 Sugar expression이라고 하는데, Software Engineer가 어떻게 하더라도 같은걸 가리키게 해준다는 의미에요. 설탕처럼 달콤하죠. 대신 조심해서 사용해야 해요. 뭐 이러면 어떻게 저러면 어떠리오. 여하간 array [번호] = * (array주소 + 번호) 인 겝니다. 대신에 재미있는 사실은 array의 이름 자체는 array 첫 번째 element의 주소라는 거지요.
예를 들어서, 배열로 하나를 선언하고서, 그 배열을 pointer로 가리키는 예제는 많이 있죠.
char *pointer;
char array[3];
array[0] = 0;
array[1] = 1;
array[2] = 2;
pointer = array;
pointer = &array[0];
마지막 두줄 은 같은 말을 의미합니다. 으흐흐. 이거 굉장히 어려운 철학이 숨어 있는 거 아니냐! 하는 의심을 버려주시기 바래요. 이걸 더 어렵게 생각하면 정말 어려운 게 pointer인데요. 이 정도로 공식화해서 알고 계시면 정말 편한 거에요. 이런 Pointer를 왜 사용하는 걸까요? 복잡하게 스리 쩝. Pointer를 사용하는 데 장점이 몇 가지가 있어요. 일단은 Pointer는 직접 주소를 가리킬 수 있기 때문에, Assembly와 많이 닮아 있는 거지요. Assembly도 직접 주소를 가리키고 만지고 저장할 수 있죠. Assembly와 엄청 닮아 있고요, 이 덕분에 각종 Memory 주소를 High Level Language인 C에서도 마음대로 Access가능하게 해주는 거죠.
또 Pointer를 쓰면 Function Call을 할 때 Passing Argument로 주소를 직접 전달 하니까, 대용량의 Data의 전달을 간단하게 주소 하나만 넘김으로써 가능하게 해주는 거지요. 원래는 대용량의 Data를 전달하려면, 값을 하나 하나 넘겨 줘야 하니까 한도 끝도 없겠죠. 무조건 그 Data를 가리키는 Pointer 하나만 넘기면 호출된 함수 (Callee)에서도 같은 Data를 주물럭 할 수 있겠죠. 그런 연유로 Data를 함수끼리 전송할 때의 Performance가 아주 좋아지고요,
그런 면에서 Array가 사용되지요. 전역변수 Array로 선언을 하기만 한다면, 연속된 주소 공간의 영역을 Arrary 이름을 이용해서 Static하게 예약 해 놓을 수 있는 것 이지요. 그러면 Array이름을 가지고, 그런 연속된 주소 공간을 pointing할 수 있는 거지요.
Caller 함수와 Callee 함수 사이의 관계에서는 Pointer를 쓰게 되면 하나 이상의 return값이 필요 할 때, 이런 식으로 여러 개의 pointer를 넘겨주고서 거기다가 결과를 넣어서 돌려줘라 하면 여러 개의 return값도 받을 수 있는 거죠. 또는 더러운 일을 Caller함수는 전혀 안 해도, Callee가 다 해주는 역할도 해주죠.
그런 예로서는 아래의 swap 함수의 예가 참으로 잘 들어 맞습니다.
int process(void)
{
int a, b;
int a = 1;
int b = 2;
swap(&a, &b);
나불나불나불~
}
void swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
return;
}
자, 보세요. process()함수는 a와 b의 주소를 swap()함수에게 넘겨줌으로써, swap() 내부에서 알아서 a와 b의 주소를 가져다가 값을 바꿔주니까, process는 손도 안대고 코풀듯이 a와 b에 뭔가 값을 return 받는 거나 다름 없는 거죠. 그리고, process()함수는 드러운 짓은 swap에게 시키고 자연스럽게 나불나불을 실행할 수 있게 되는 거지요.
댓글