오늘은 제 생일이야요. 생일 답게 멋들어진 글을 올리고 싶지만,
연일 계속 되는 격무에, 진도만 빼지요 ㅋㅋ.
의외로 struct를 잘 다루지 못하는 중급 이상의 Engineer들이 있어서. strcut에 대해서 걸치고 넘어가겠사옵니다. 언제나 기본이 우리를 무식쟁이들로 만드는거니까, 기본 한개 더 짚고 넘어간다고 해서 뭐라 하진 않겠죠.
struct는 structure를 가진 변수를 만드는 만드는 마술사에요.
struct customer {
char *name;
int height;
int weight;
}
요렇게 하면 customer라는 struct를 창조해 낸 겁니다. 그러니까 customer는 보통 얘기하는 int나, char과 같은 거에요. 그러니까, customer kim; 이런식으로 선언하면 customer형태의 kim을 선언한 거에요. 여기에서 잠깐, customer kim; 을 한방에 선언할 수 있는 방법이 있죠.
struct customer {
char *name;
int height;
int weight;
} kim;
그게 요런거에요. 그러니까, customer라는 Data 형태를 가진 kim이라는 변수를 선언한 거죠. 뭐 kim을 이제부터 array나 pointer로도 선언할 수 있겠죠.
struct customer {
char *name;
int height;
int weight;
} kim[100]; 이라든가,
struct customer {
char *name;
int height;
int weight;
} *kim; 이라든가 하는 걸로요.
뭐, 간단하죠? 그런데 이렇게 하다 보니까, 좀 불편하더란 말이죠. customer kim[100]; 이라던가, customer *kim; 라던가 하는 형태가 좀 불편한 거에요. 왜냐, 이쁘지가 않은 거죠. 그래서 customer를 다른 type으로 정의해 놓고 쓰는 방법을 많이 쓰죠. 그래서 보통은 struct를 다룰 때typedef과 같이 많이 쓰죠
typedef가 하는 일은 기존의 Data형으로 새로운 Data형을 만드는 거에요. 예를 들면,
typedef unsigned char byte; : Unsigned 8 bit value type.
typedef unsigned char uint8; Unsigned 8 bit value.
typedef unsigned short word; Unsinged 16 bit value type.
typedef unsigned long dword; Unsigned 32 bit value type.
뭐, 이런식인거죠. 결국엔 char a;를 하나, byte a;를 하나, uint8 a;를 하나 다 똑같다는 말이죠~ 자기가 보기 편한 data형으로 이름을 바꿀 수가 있는거지요. 그러니까,
typedef struct customer {
char *name;
int height;
int weight;
} kim;
이렇게 하게 되면 이제 부터는 kim은 더 이상 변수가 아닌 거지요. customer struct를kim struct로 재정의 했으니까, kim lee; 뭐 이런식으로 kim을 Data형으로 재정의 한 거에요. 이렇게 되면
typedef struct customer {
char *name;
int height;
int weight;} kim[100];
이라든가, - 이건 마치 int[100]이라든가, char[100] 과 같은 말이에요.
typedef struct customer {
char *name;
int height;
int weight;
} *kim;
등은 불가능해 지고요 이제부터는 kim lee[100], kim *lee 뭐 이런식으로 선언해서 사용해야 하는거죠. 보통은 kim의 자리에 cutomer_type 이라던가 type의 의미를 덧붙여서 헷갈림을 방지해서 사용하죠.
typedef struct customer {
char *name;
int height;
int weight;
} customer_type;
이렇게 하면, 이제부터는 customer_type kim;이라든가, customer_type kim[100]; 이라든가customer_type *kim; 이라던가 가능해 지는거죠. 오, 좀 이뻐졌죠? 야옹.그리고 재귀적으로 자기가 자기 모양을 갖는 member를 갖게 하고 싶은 경우가 있는데, 어디에 많이 쓰이냐면, linked list 같은 곳에 많이 쓰이죠. node의 형태로요
뭐, 예를 들어,
typedef struct heap_node {
char filename [SIZ_FILE_NAME];
unsigned int line_number;
void * allocated;
struct heap_node *next;
} heap_node_type;
이거 해석 되나요? 바로 그거죠. heap_node가 heap_node를 가지고 있는 구조죠?이거야 말로 재미있는 구조인게죠. 으흐흐. 또 typedef가 많이 쓰이는 case가 있는데요, 이건 여러가지 경우를 갖는 case를 구현할 때 많이 쓰여요. 예를 들면 enum type에 관련된 내용인데,
typedef enum {
START,
WALK,
RUN
} customer_activity_type;
customer_activity_type activity;
이런 식으로 type을 만들고서, 그 변수를 선언하면, activity는 START (0), WALK (1), RUN (2)의 3가지 enum값을 갖는 변수가 탄생하게 되는 거죠. 이런걸 이용해서
switch (activity)
{
case START:
뭐뭐뭐
break;
case WALK:
뭐뭐뭐
break;
case RUN:
뭐뭐뭐
break;
}
뭐 이런 식의 activity 처리가 가능한 거에요. 그리고, __packed 라는 지시어가 struct와 많이 같이 사용되는데요, 이건 어디다 쓰느냐. 하면! (여기서 int를 4byte라고 가정하면요)
ⓐ
typedef struct customer {
char name;
int height;
int weight;
} customer_type;
ⓑ
typedef __packed struct customer {
char name;
int height;
int weight;
} customer_type;
요거 두개의 차이를 보면 극명해 지는데요, ⓐ의 경우 총 몇 byte를 차지 할까요?
이론적이라면 1 byte + 4 byte + 4 byte = 9 byte를 차지해야겠죠?
아니아니죠.
ⓐ의 경우에는 총 Memory를 차지하는게 12 byte를 차지하고요,
ⓑ의 경우에는 총 9 byte를 차지해요.
이거는 __packed를 사용할 때와 안 할 때의 차이인데요, __packed를 쓰게 되면 byte alignment을 하게 되고요, __packed를 안 쓰게 되면요, struct내부에서 실제 자기 크기하고는 상관없이 4byte 단위로 Data를 alignment해요.(다른 System은 안그런데, ARM은 고전적인 성능 문제로 그렇게 처리합니다) 대신 4 byte 이내의 것들은 한번에 붙여 씁니다. 그러니까 예를 들면,
typedef struct customer {
char name; /* 1byte + 쓰레기 3byte */
int height /* 4 byte */
} customer_type; /* 총 8 byte */
이런 식이 되고요, 만약에 첫번째 4byte에 다른 member가 들어오면 쓰레기 padding 2byte가 들어가는 거죠.
typedef struct customer {
char name; /* 1byte */
char flag; /* 1 byte + 쓰레기 2byte */
int height /* 4 byte */
} customer_type; /* 총 8 byte */
만약 4byte가 넘는 8 byte짜리가 들어오면 어케 되느냐구요? 이렇게 됩니다.
typedef struct customer {
char name; /* 1byte + 쓰레기 3byte */
double height; /* 8 byte */
int weight; /* 4 byte */
char office; /* 1byte + 쓰레기 3byte */
} customer_type /* 총 20 byte */
이렇게 되는 거죠. 켁. 마지막 customer_type의 member에 char type의 변수 하나를 추가해도 1byte뒤에 쓰레기가 있으니까 그대로 20byte를 유지하게 되는 거에요. - 이 부분의 다른 System에 관련된 Argue는 Q&A란에 옮겨 놓았사옵니다. -
참고로 한가지 예만 더 들어보면, 4byte alignment에 대한 이해가.. 쩝.
typedef struct customer {
char name; /* 1byte */
char height; /* 1byte */
char weight ; /* 1byte + 쓰레기 1byte */
} customer_type /* 총 4 byte */
어때요! - 이런 예시 쟁이 같으니라고 - 참고로 sizeof()를 하게되면 3이 나올텐데요, 말그대로 struct는 3byte를 잡지만 메모리 입장에서는 못쓰는 쓰레기 1byte가 실제로는 있다구요!
그러니까 char이라도 4 byte의 크기를 갖는 거에요. 그러니까, char자리는 1byte는 data로 쓰고요, 3byte는 낭비를 하고 있는 거죠. __packed를 사용하면 선물 package를 만들 듯이 빈 공간 없이 꽉꽉 채워 넣는 거라지요. 이런 packed가 왜 필요한가! 하면 서로 다른 System끼리 Packet통신을 할 때가 문제가 발생해요. 예를 들어, Embedded System과 PC Host가 서로 USB로 통신을 한다고 했을 때, PC는 int를 4byte로 인식하고 Embedded System에서는 int를 2byte로 인식한다면, 서로 같은 Data를 쓰더라도 막상 packet형태로는 다르게 인식한다는 말이죠. 그래서 그런 통신 문제들을 해결하기 위하여, 최소한 Data Packet으로 만드는 Data의 byte alignment는 byte로 하자는 목소리가 커진 거에요. 게다가 packet에 Data를 꽉꽉 채워 넣으면 성능 면에서도 훨씬 유리하겠죠.
그래서 사용하는 게 __packed이고요, __packed struct는 ARM사의 ADS가 이런 식으로 표기하고요, GNU gcc 진영에서는 type이름 앞에다가 __attribute__ ((packed)) 요런걸 붙여서 표현한답니다.
typedef struct customer {
char *name;
int height;
int weight;
} __attribute__ ((packed))customer_type;
그럭저럭 적당히 하다가는 묘한 통신 상태를 만들어 낼 수도 있어요. 뭐, 별거 없지만, 이런거 잘 모르면 System만들어 놓고, 왜 동작 안하는지 이해 못할 때도 엄청 많다니깐요.
댓글