brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Aug 24. 2016

구조체, 공용체, 열거형

오 놀라워라~ C언어의 확장성 III




구조체와 포인터의 만남 ii: ->의 등장



구조체를 선언하고 그 구조체 그대로 함수의 인자로 사용하거나 함수의 리턴 값으로 활용할 수 있다는 것을 배웠습니다.

그래서 간혹 배열의 요소 전체가 통째로 리턴 되지 못하는 문제를 그 배열을 구조체로 감싸 안아 해결하는 사람들도 있습니다. 별로 좋은 방법은 아닙니다.

단지 주소만 넘기는 배열과는 전혀 다른 이 멋진 구조체의 놀라운 확장성은 완벽한 것이 아닙니다. 구조체의 특성상 멤버들이 많아지게 되면 메모리를 많이 사용하게 되고 필요 없는 메모리 조각까지 포함한 채 거대한(?) 메모리 덩어리들이 스택을 왔다 갔다 하게 되면 결코 프로그램에 좋은 영향을 주지는 못합니다.

때문에 구조체 자체를 넘기거나 받는 것 보다는 구조체의 주소를 사용하는 것이 일반적인 프로그래밍 방법입니다.

구조체가 주소 값으로 넘어오게 되고 그 주소 값을 사용하여 멤버에 접근하려면 보통은 다음과 같은 수식을 사용하게 됩니다.


(*Structure).member 이렇게 사용하면 Structure가 주소 값이라는 것을 분명히 알 수 있습니다. () 연산자를 사용하여 * 연산자의 우선순위를 높였기 때문입니다.

일반적으로 *와 () 그리고 . 이 세가지 연산자를 사용해야 하는 것을 -> 연산자 하나로 해결합니다. -> 연산자 안에는 *와 () 그리고 . 연산자가 함께 들어 있다는 것을 기억해야 합니다.

그리고 더욱 놀라운 것은 여기에서도 포인터의 속성을 그대로 가지고 있기 때문에 Sinclair의 정리 1번이 그대로 먹힌다는 것입니다.

그러므로 아래 세가지 수식은 같은 결과를 갖게 됩니다.

이제 세가지 중 어떤 것을 사용해도 됩니다.


Structure->member를 가장 많이 사용하고 있기는 하지만 바보 씽클레어는 Structure[0].member도 즐겨 사용합니다. ㅎㅎㅎ


(*Structure).member == Structure->member == Structure[0].member

                                        != *Structure.member



*(*Structure).member == *Structure->member == *Structure[0].member

                                         == Structure[0].member[0]


(*(Structure+i)).member == Structure[i].member == (Structure+i)->member



여기에서도 포인터의 원칙은 결코 변하지 않습니다. 제가 감히 C언어는 산수다 라고 했습니다. 그 까닭은 우리가 구구단을 외워 복잡한 수식을 곱하거나 나눌 수 있는 것처럼 아주 작은 원리가 모든 C 언어의 모든 영역에 그대로 적용되기 때문입니다.  



#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototypes

struct array getArray() ;

int setArray(struct array *) ;  

struct data { char c ; int i ; } ; // 이렇게 한 줄로 선언해도 됩니다.

struct array {

    int matrix[3][4] ;

} ;  


int main() {

    struct data datum ;

    struct data * datumPtr ;

    struct array mat, matt ;

    int i ;

    datumPtr = &datum ;

    printf("%d\t%d\n", sizeof(struct data), sizeof(struct data *)) ;

    printf("%d\t%d\n", sizeof(datum), sizeof(datumPtr)) ;

    printf("%d\t%d\n", sizeof(datum.c), sizeof(datum.i)) ;

    printf("%d\t%d\n", sizeof (*datumPtr).c, sizeof (*datumPtr).i) ;

    printf("%d\t%d\n", sizeof datumPtr[0].c, sizeof datumPtr[0].i) ;

    printf("%d\t%d\n", sizeof datumPtr->c, sizeof datumPtr->i) ;

    mat = getArray() ; // 리턴도 잘 하고 대입 연산자도 잘 먹습니다.

    setArray(&matt) ;

    for ( i = sizeof mat.matrix / sizeof **mat.matrix ; i > 0 ;) {

        printf("%3d", mat.matrix[0][--i]) ;

    } // end for

    putchar('\n') ;

    for ( i = sizeof matt.matrix / sizeof **matt.matrix ; i > 0 ;) {

        printf("%3d", matt.matrix[0][--i]) ;

    } // end for

    putchar('\n') ;

    return 0 ;

} // end main()  


struct array getArray() {

    struct array m ;

    register i ;

    for ( i = sizeof m.matrix / sizeof **m.matrix ; i > 0 ;) {

        m.matrix[0][--i] = i ;

    } // end for

    return m ;

} // end setArray()


int setArray(struct array * m) {

    register i ;

    for ( i = sizeof m->matrix / sizeof **m->matrix ; i > 0 ;) {

        m->matrix[0][--i] = i ;

    } // end for

    return 0 ;

} // end setArray()   




아래와 같이 구조체 타입을 typedef로 선언할 때 그 구조체 변수의 주소 값을 저장할 포인터 타입도 함께 선언하는 것이 요즘 프로그래밍 추세라고 합니다.

Data 타입으로 선언된 변수는 .연산자로 접근합니다. DataPtr 타입으로 선언된 변수라면 ->연산자나 [] 와 .연산자로 접근하면 됩니다.

그리고 실제로 구조체 배열의 멤버를 참조해야 할 때도 일반적으로 Structure[i].member라고 접근해야 하지만 (Structure+i)->member로 사용해도 같은 결과가 됩니다.  


typedef struct _data {

    char c ;

    int i ;

} Data, * DataPtr ;


Data datum = { '+' , 153 } ; // 1!+2!+3!+4!+5! == ??


DataPtr datumPtr ;

datumPtr = &datum ;


printf("%d\t%d\n" , sizeof(Data), sizeof(DataPtr)) ;

printf("%c\t%d\n" , datum.c , datum.i) ;

printf("%c\t%d\n" , datumPrt->c , datumPrt->i) ;  



이렇게 구조체 포인터를 동적 할당해 사용할 때는 다만 ->연산자가 등장했다는 것 말고는 일반 포인터의 경우와 전혀 다르지 않습니다. 만약에 구조체 포인터가 포인터 멤버도 포함하고 있다면 구조체에 대한 동적 할당을 하고 각 구조체의 멤버들도 동적 할당하면 됩니다. 그리고 반드시 동적 할당한 만큼 free() 하는 것도 잊지 말아야 합니다. 초기화와 사이즈, 이 두 가지만 기억하면 어려울 것이 전혀 없습니다.  


#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototypes

extern int fgets4(register char *, int) ;

extern char * strdup2(char*) ;

extern int onltInt(int *) ;

extern int getMemory2(void *, size_t) ;

typedef struct record {

    int id ;

    char * name ;

} Record , * RecordPtr ;  


#define __BUFFSIZ___    80

#define FREE(p)            (free(p),p=NULL)


int main() {

    RecordPtr rp ;

    char buffer[__BUFFSIZ___] ;

    int i , j , people ;

    printf("%s" , "How many people? ") ;

    if( onlyInt(&people) ) {

        return -1 ;

    } // end if


    if( getMemory2(&rp , sizeof(Record)*people) ) { // free(rp)

        return -2 ;

    } // end if


    for ( i = people ; i > 0 ; )    {

        rp[--i].id = 10000+people-i ;

        printf("%s" , "What is your name? ") ;

        fgets4(buffer , __BUFFSIZ___) ;

        if(!(rp[i].name = strdup2(buffer))) { // free(rp[i].name)

            for ( j = people ; j > i ; ) {

                FREE(rp[--j].name) ;

                FREE(rp) ;

                return -3 ;

            } // end inner for

        } // end if

    } // end outer for

    for ( i = people ; i > 0 ; ){

        printf("[%d]: id = %d\tname = %s\n",

                people-i, (rp+i)->id,(rp+ --i)->name) ;

    } // end for  

    for ( i = people ; i > 0 ; ) FREE(rp[--i].name) ;

    FREE(rp) ;

    return 0 ;

} // end main()  




보시는 바와 같이 결코 어렵지 않습니다. 혹시 '너나 어렵지 않지~' 라고 생각한다면 제가 할말이 없습니다. 부디 마음과 생각을 비우고 포인터의 진실부터 다시 한 번 공부하길 바랍니다. 열심히 하는 건 정말 중요하지만, 잘못된 방법으로 열심히 하면 그건 곧 죄악이 될 수도 있습니다.

어렵다고 포기했다면 지금 여러분의 현재 위치에 있을 수 있었겠습니까? 이 세상에 저절로 되는 건 하나도 없습니다.

 





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

매거진의 이전글 구조체, 공용체, 열거형
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari