C - 구조체, 공용체, 함수 포인터
업데이트:
구조체
typedef
-
구조체에
typedef
쓰면 간결하게 변수선언이 가능 -
struct date { int year; int month; int day }; typedef struct date date_t; typedef struct date { int year; int month; int day; } date_t typedef struct { int year; int month; int day; } date_t; // struct date 로 접근 불가 date_t date;
enum
/union
도 마찬가지다
초기화
-
구조체 선언과 동시에 초기화되지 않음. 스택에 남아있는 가비지 데이터 그대로 사용함
-
초기화 방법
-
멤버 변수에 직접 0으로 대입하거나
-
date_t date = { 0, }
-
구조체 매개변수
구조체는 값형이다.
- 멤버 변수가 포인터형이 아닌 이상은 데이터 변경이 불가능하다.
- 구조체를 매개변수로 받는 함수 내에서 구조체의 멤버 변경해도 함수 밖에서는 변경되지 않는다.
구조체 포인터에서 멤버의 값에 접근하기
-
void increase_year(date_t* date) { (*date).year = (*date).year + 1; // 연산자 우선순위 때문에 괄호 필요 date->year = date->year + 1; // 위와 동일한 결과 date->year++; // 위와 동일한 결과 }
구조체 매개변수 베스트 프랙티스
-
값으로 전달 vs 주소로 전달
- 구조체의 경우 데이터 크기가 클 수 있음. 기본 자료형은 데이터 크기가 작음.
- 전체 멤버변수를 복사하는 게 성능이 느릴 수 있음
- 포인터에
const
포인터를 붙이면 원본도 못 바꾸니 안전함
- 구조체의 경우 데이터 크기가 클 수 있음. 기본 자료형은 데이터 크기가 작음.
-
구조체 매개변수 vs 여러 개의 개별 변수?
- 3~5개까지는 낱개 변수로, 그 이후에는 구조체로 넘기자
- 구조체를 사용하면 실수를 줄일 수 있고, 주소로 전달할 경우 성능을 빠르게 할 수 있다.
얕은 복사, 깊은 복사
- 얕은 복사 : 실제 데이터가 아니라 주소를 복사하는 걸 얕은 복사라고 함
- 깊은 복사 : 구조체 변수마다 독자적인 메모리 공간을 만들어주고 문자열을 복사해야 함
파일을 쓸 때도 문자열이 아닌 주소값을 쓰지 않도록 주의해야 한다.
주소값이 아닌 배열을 복사하여 매개변수로 전달하는 방법
enum { NAME_LEN = 32 };
typedef struct {
char firstName[NAME_LEN]; // 포인터가 아닌 배열로 선언하면 된다.
char lastName[NAME_LEN];
} name_t;
베스트 프랙티스
- 가능한 한 덩어리 메모리에 모든 데이터가 들어가고 대입 가능한 구조체를 만들자. 복사가 가능하다
- 즉, 포인터만 없으면 됨
구조체를 구조체의 멤버로
struct user_info_t {
unsigned int id; // 4 byte
name_t name; // 64 byte
unsigned short height; // 2 byte? X -> 4 byte
float weight; // 4 byte
unsigned short age; // 2 byte? X -> 4 byte
};
- 4바이트를 안 채운 애들이 쓸데없는 공간을 먹음
- 시스템 상의 제약이 있거나 효율성 때문
- 각 시스템마다 메모리에 접근 할 때 사용하는 주소에 대한 요구사항이 다름
- 특정 시스템은
n
바이트 배수인 시작 주소에서만 메모리 접근 가능 - X86 시스템은 4바이트(워드 크기) 경계에서 읽어오는게 효율적
- 이걸 4 바이트 경계에 정렬된다(aligned)고 함
- 컴파일러가 알아서 각 멤버의 시작 위치를 경계에 맞춤
- 32비트 clang 윈도우는 4바이트 정렬
패딩 줄이기
struct user_info_t {
unsigned int id; // 4 byte
name_t name; // 64 byte
unsigned short height; // 2 byte
unsigned short age; // 2 byte
float weight; // 4 byte
};
- 2개의 short 형 변수가 4바이트로 합체! (안 해 줄 수 도)
구조체 베스트 프랙티스
-
구조체를 파일 등에 저장해야 해서 바이트 크기가 정확히 맞아야 한다면?
-
assert()
를 사용해서 크기를 확인#include <assert.h> assert(sizeof(user_info_t) == 76);
-
구조체에 패딩을 명시
struct user_info_t { unsigned int id; // 4 byte name_t name; // 64 byte float weight; // 4 byte unsigned short height; // 2 byte unsigned short age; // 2 byte char unsused[2]; };
-
비트 필드
c에서 구조체를 사용하면 간단히 비트 플래그 구현 가능
typedef struct {
unsigned char b0 : 1; // 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; // 총 1byte
공용체
- 똑같은 메모리 위치를 다른 변수로 접근하는 방법
- 즉, 공용체 안에 있는 여러 변수들이 같은 메모리를 공유
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;
} bigflags_t;
// val 은 8개 비트를 한번에 1byte char로 읽는다.
함수 포인터
함수 포인터, 함수를 변수에 저장할 수 있을까?
- 함수의 시작 주소를 변수에 저장하면 되겠다!
함수의 파라미터로 함수를 전달할 수 있을까?
- 함수 코드의 시작 메모리 주소를 파라미터로 전달하고
- 호출된 함수 내부에서 함수 파라미터 주소를 실행하면 되겠다!
함수를 매개변수로 전달할 때 필요한 것들
- 자기 자신이 받아야 하는 매개변수 목록
- 자기 자신이 반환하는 자료형
- 호출된 함수는 파라미터 함수의 시그니처를 알 방법이 없음! 그냥 4byte 주소값
함수 포인터 변수의 선언과 사용
double add(double x, double y) {
return x + y;
}
double (*funcVar)(double, double) = add;
result = funcVar(op1, op2)
함수 포인터 매개변수의 선언과 사용
double calculate(double, double, double, (*)(double, double));
double calculate(double x, double y, double (*funcVar)(double, double)) {
return funcVar(x, y);
}
result = calculate(op1, op2, add)
void *
- 범용적 포인터
- 어떤 포인터라도 대입 가능
- 어떤 변수의 주소라도 곧바로 대입 가능
- 단 다음의 경우 캐스팅 또는 대입해서 써야 함
- 역 참조( 몇 바이트 읽을 지 모름 )
- 포인터 산술 연산 ( 몇 바이트 이동할지 모름 )
댓글남기기