구조체를 사용하는 이유?
구조체에 typedef
를 쓰면 다른 자료형처럼 간결하게 변수 선언이 가능 (공용체, 열거형도 동일하게 사용가능)
// 사용법 1
struct date{
int year;
int month;
int day;
};
typedef struct date date_t;
// 사용법 2
typedef struct date{
...
} date_t;
// 사용법 3
typedef struct {
...
} date_t;
date_t date;
date.year = 2022;
date.month = 10;
date.day = 1;
구조체 멤버변수 접근
/* 매개변수가 포인터(주소값이라면) */
-> // 포인터의 값에 접근해서 멤버변수 가져오기
date->year
/* 매개변수가 구조체(값이라면) */
. // 구조체의 멤버변수 가져오기
(*date).year
구조체 매개변수 베스트 프랙티스
구조체는 원본이 안바뀌어도 데이터의 크기가 크다면 주소로 전달하는 것이 효율적이다.
기본 자료형은 원본바뀔 때만 주소로 전달하면 되었음.
주소를 전달했다가 원본이바뀔까 걱정된다면 const
키워드 사용하면된다!
매개변수가 10개쯤 된다면 구조체로 변경해서 전달해주는 것이 좋다.
다음 구조체 변수를 대입 하면 내부적으로 어떤 일이 일어날까?
typedef struct {
char* firstname;
char* lastname;
} name_t;
name_t name;
char [] firstname = "Lee";
char [] lastname = "lulu";
name.firstname = firstname; // firstname은 포인터 변수 : firstname의 주소값을 저장
name.lastname = lastname; // lastname은 포인터 변수 : lastname의 주소값을 저장
name_t clone = name; // clone.firstname 공간에 name.firstname의 값(주소값)들어간다
clone.firstname[0] = 'Q'; // 복사본, 원본 모두 바뀐다. firstname 변수 값 자체도 바뀐다.
printf("%s / %s / %s", clone.firstname, firstname, name.firstname);
얕은복사
깊은복사
NAME_LEN = 32;
typedef struct {
char firstname[NAME_LEN];
char lastname[NAME_LEN];
} name_t;
void check_size (name_t name){
size_t size;
size = sizeof(name); // size:64
// --> 원본의 크기 그대로 함수의 매개변수가 전달된다.
}
typedef struct {
unsigned int id; // 4바이트
name_t name; // 64바이트
unsigned short height; // 2바이트
float weight; // 4바이트
unsigned short age; // 2바이트
} user_info_t; // 76바이트?
// 실제 크기는 80byte
// 실제 차지하고 있는 메모리 구하기.
user_info_t info;
int off_id = (char*)&info.id - (char*)&info; // 0
int off_name = (char*)&info.name - (char*)&info; // 64
int off_height = (char*)&info.name - (char*)&info; // 68 ?
int off_weight = (char*)&info.name - (char*)&info; // 72
int off_age = (char*)&info.name - (char*)&info; // 76
원래 2byte인 short
자료형이 4byte씩 공간을 차지하고 있다.
시스템마다 데이터를 읽어오는 단위가 정해져있다.
x86시스템은 4바이트(워드크기) 경계에서 읽어오는게 효율적
2바이트 남는 공간을 그냥 무시하고 4바이트씩 사용하도록 컴파일러가 알아서 세팅 (패딩)
문제는 다른 아키텍쳐 시스템에서는 다른 방식으로 읽어온다면.. 데이터가 깨져서 제대로 사용 못할 수 있다.
방법은?
프로그래머가 4바이트 맞춰서 끊어주자 --> short 변수 2개를 순서대로 저장하기
char unused[2];
)assert()
사용해서 크기를 확인
#include <assert.h>
assert(sizeof(user_info_t) == 76);
바이트 단위가 아닌 비트단위의 데이터형을 가지고 싶을 때
typedef struct {
unsigned char b0 : 1;
unsigned char b1 : 1;
unsigned char b2 : 1;
unsigned char b3 : 1;
unsigned char b4 : 1;
unsigned char b5 : 1;
unsigned char b6 : 1;
unsigned char b7 : 1;
} bitflags_t;
// size : 1 (1바이트 : 8비트)
포인터를 사용해서 한번에 비교도 가능 (꼼수.. 원래는 공용체로 해야댐)
char* val;
int is_zero;
bitflags_t flas = {0, };
flags.b3 = 1;
val = (char*)&flags; // flags를 1바이트 단위로 읽는 포인터 val
is_zero = (*val == 0); // val의 값이 0이면 모든 비트플래그가 0
똑같은 메모리 위치를 다른 변수로 접근하는 방법 (공용체 안에 여러 변수들이 같은 메모리를 공유)
여러가지 변수 형태로 같은 메모리를 읽는 방법!
typedef union {
unsigned char val;
struct {
unsigned char b0 : 1;
unsigned char b1 : 1;
unsigned char b2 : 1;
unsigned char b3 : 1;
unsigned char b4 : 1;
unsigned char b5 : 1;
unsigned char b6 : 1;
unsigned char b7 : 1;
} bits;
} bitflags_t;
val
과 구조체 bits
는 같은 주소를 가리키고 있다! (둘다 포인터)위에서 포인터로 한 것을 공용체로 해줄 수 있다.
int is_same;
int is_zero;
bitflags_t flags = {0, };
flags.bits.b1 = 1;
flags.bits.b4 = 1;
is_same = (flags.bits.b1 == flags.bits.b7);
is_zero = (flags.val == 0);
공용체 사용 예시 (색상표현)
typedef union {
unsigned int val;
struct {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a; // 투명도 00 ~ FF (불투명)
} rgba;
} color_t;