brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Aug 01. 2016

Advanced Pointer I

록 삼총사와 친구들 I




우리는 앞에서 이미 포인터에 대한 새로운 패러다임을 접했습니다. 


늬들이 포인터를 알아? I     

늬들이 포인터를 알아? II


포인터가 등장하면 무엇보다 먼저 생각할 것이 초기화되었는지 확인하는 일입니다. 


그리고 사용할 때는 포인터가 혼자 단독으로 나다니지 않게 조심, 또 조심해야 합니다. 

일반적으로 선언할 때 사용한 *의 개수만큼 사이즈도 함께 다녀야 합니다. 


이 두 가지만 꼭 기억한다면 새로운 패러다임으로 접한 포인터는 그다지 어렵지 않습니다. 초기화하는 방법으로 상수 값이나 수식을 사용하는 것은 이미 앞에서 설명했습니다. 이번 글타래에서는 함수로 초기화하는 일반적인 방법인 동적 할당을 이야기하도록 하겠습니다. 


학교에서 수업 중에 배울 때 동적 할당은 그냥 malloc() 함수를 쉽게 사용하여 별로 어렵지 않은 것처럼 보입니다. 하지만 과학 science과 공학engineering에는 많은 차이가 있는 것 같이 실제 필드에서 개발할 때 학교에서 배운 대로 그냥 작성하면 머지않아 엄청난 문제를 만나게 될 것입니다.


사실 표준 함수들 중에 포인터를 리턴 하는 것은 아주 일부분입니다. 그나마 문자열 관련된 함수들을 제외하고 나면 한 손으로도 충분히 꼽을 정도입니다. 무슨 이야기인가 하면 일반적으로 char*를 제외한 나머지 포인터를 리턴 해서는 안 된다는 것입니다. 우리가 잘 알고 있듯이 원래 리턴 값은 없거나 하나입니다. 

하지만 조금 전에 포인터는 늘 사이즈와 함께 다녀야 한다고 했습니다. 포인터만 딸랑 리턴 하면 절대 네버 안 된다는 말입니다. 얼마나 많은 위험이 도사리고 있는지 아무도 가르쳐 주지 않았다고 나중에 후회해야봐야 소용없습니다. 물론 포인터와 사이즈를 한번에 리턴 하려면 구조체로 감싸고 그 구조체를 리턴 하는 것이 한 방법입니다. 하지만 벼룩 한마리 잡으려고 초가삼간 다 태우는 소리입니다. 쀍~! 배보다 배꼽이 더 커지는 순간이죠. 안되는 건 없습니다. 하지만 효율을 생각해야합니다. 


그럼에도 불구하고 유일무이하게 char*는 그냥 리턴 해도 되는 이유가 멀까요? 그것은 char*의 경우 널 문자를 만나면 끝이기 때문입니다. 그냥 리턴 해도 리턴 받은 쪽에서 strlen() 함수로 사이즈를 알아낼 수 있습니다. 그래서 문자열 처리 함수들을 제외하면 실제 초기화할 때 호출하는 함수는 거의 대부분 malloc(), calloc(), realloc() 함수들과 그것을 활용한 확장함수들이 뿐입니다.


저는 특별히 heap영역에 사용할 메모리를 할당하고 void*를 리턴 하는 이 세 함수를 묶어서 록 삼총사라고 부릅니다. 우리가 록 삼총사를 부를 때는 주의해야 할 것이 있는데 그것은 바로 프로그램이 종료하기 전에 록 삼총사를 부른 횟수만큼 반드시 free() 함수도 불러줘야 한다는 것입니다. 

그리고 또 한가지는 프로그램을 작성하는 동안 사이즈가 명확한 것에 대해서는 굳이 록 삼총사를 불러 동적 할당 하지 말라는 것입니다. 

동적 할당은 정말 멋진 프로그래밍 방법이고 굉장히 있어 보이기 까지 합니다. 그러나 진정한 고수는 필살기를 함부로 내보이는 일이 없습니다. 이 멋진(?) 동적 할당에는 응당 대가가 따르기 마련입니다. 함수를 호출하기 때문에 일반적으로 배열로 할당하는 것보단 느려집니다. 그리고 끝나기 전에 반드시 free()도 해줘야 합니다. 명심하길 바랍니다. 필살기는 때로 자신을 죽음에 몰아 넣기도 합니다. 굳이 있어 보이고 싶다면 그냥 (static) 배열로 잡고 포인터인 것처럼 사용하면 되지 않습니까? 

록 삼총사는 반드시 꼭 필요할 때만 부르되, 반드시 프로그램 종료 전에 free()도 함께 불러야 한다는 것을 기억한다면 여러분들은 이미 고수의 반열에 한 발짝 다가 선 것입니다. 대부분의 컴파일러의 경우 록 삼총사는 <stdlib.h>에 선언되어 있습니다. 

또한 여기에 void*가 등장하고 있습니다. void*는 타입과 관계없이 어떤 종류의 주소 값이든 담을 수 있는 범용 포인터와 같습니다. 다만 void*는 이것이 주소 값이라는 정보 외에 어떤 것도 가지고 있지 않기 때문에 참조를 비롯한 거의 모든 연산이 불가능합니다. 때문에 반드시 저장할 당시의 타입을 반드시 기억하고 있어야 캐스트 연산자를 통해 데이터를 다시 불러올 수 있습니다.  



#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/ 


#define sourceData( vp , type ) ( *(type*) vp ) 

int main() { 

    void * vp ; 

    int iData = 100 ;

    float fData = 0.1f ;

    double dData = 3.141592 ;

    int * ip = &iData ; 

    struct datum {

        int i ;

        char c ;

        double d ;

    } data = { 200 , 'D' , 1.414213 } ; 

    vp = &iData ;

    printf("int = %d \n" , sourceData(vp ,int) ) ; 

    vp = &fData ;

    printf("float = %f \n" , sourceData(vp ,float) ) ; 

    vp = &dData ;

    printf("double = %f\n" , sourceData(vp ,double) ) ; 

    vp = &ip ; // int **

    printf("int * = %p int = %d\n" , sourceData(vp ,int*)

         , *sourceData(vp ,int*) ) ; 

    vp = &data ;

    printf("%d %c %f\n" , sourceData(vp , struct datum).i

                 , sourceData(vp , struct datum).c

                 , sourceData(vp , struct datum).d) ; 

    return 0 ; 

} // end main()  









#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 Advanced Pointer I
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari