[C언어] #6 구조체, 공용체

Ilhoon·2022년 2월 18일
1
post-thumbnail

구조체를 사용하는 이유?

  • 인간의 이해를 돕는다.
    • 컴퓨터는 구조체 개념을 모른다 (그냥 지역변수 여러개를 사용하기 쉽게 묶어놓은 것)
  • 매개변수가 여러개 나열될 때 이름이 있기 때문에 실수를 줄일 수 있다. (하나를 빠트리거나, 순서가 뒤바뀌거나..)

typedef

구조체에 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개를 순서대로 저장하기

      • 4바이트 딱 떨어지지 않을 때는 사용하지 않는 바이트를 구조체에 같이 포함 (ex 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;
profile
꾸준하게!

0개의 댓글