구조체
// C에서는 struct 키워드를 매번 써야 함
struct Point {
int x, y;
};
struct Point p1; // C 스타일
// typedef를 사용하면 struct 키워드 생략 가능
typedef struct {
int x, y;
} Point;
Point p1 = {10, 20}; // struct 키워드 불필요
- 하나의 주체가 여러 설정을 가지고 있는 경우 등
- Vector2f 데이터형도 구조체다.
- 사용자 정의 열거형과 같은 ‘사용자 정의 데이터형’이다.
struct { float x, y; } pos; // 익명 구조체
pos.x = 1.5;
pos.y = 2.2;
printf("좌표 : (%f, %f)\\\\n", pos.x, pos.y);
- 위와 같은 형태는 예시용이고 되도록 사용하지 않는다.
- 구조체의 이름없이 사용하는 형태이기 때문이다.
- 익명 구조체 라고 한다.
- 일회용이다.
- 구조체의 이름없이 사용하는 형태이기 때문이다.
구조체 구성 요소
- struct : 구조체 선언 키워드
- { float x, y; } : 구조체 멤버 정의
- 멤버: 구조체를 구성하는 개별 변수들
- 여러 타입의 변수 포함 가능
- pos : 구조체 변수 이름
struct ProductInfo { // 구조체 타입 이름
int num; // 멤버 1: 제품 번호
char name[100]; // 멤버 2: 제품 이름
int cost; // 멤버 3: 제품 가격
};
int main() {
ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
printf("제품 번호 : %d\\\\n", myProduct.num);
- 구조체 메모리 : 안의 요소의 데이터형의 바이트 크기 합만큼의 메모리가 구조체 객체를 생성할때마다 생성된다.
- 위의 메모리 크기 : 4 + 100 + 4 : 108바이트
- 메모리 크기를 공유하는것이 아니라 개별적인 방이 있다고 생각해야한다.
- 해당 데이터형의 메모리에만 읽고 써지는 것이다.
- .num으로 사용하게된다.
구조체 초기화 방법
// 초기화와 함께 선언
ProductInfo product1 = { 1001, "키보드", 50000 };
ProductInfo product2 = { 1002, "마우스", 30000 };
// 선언 후 개별 대입
ProductInfo product3;
product3.num = 1003;
strcpy(product3.name, "모니터"); // 문자열은 strcpy 사용
product3.cost = 200000;
// 부분 초기화
ProductInfo product4 = { 1004 }; // name은 "", cost는 0
//전체 복사
ProductInfo product5 = product3;
- 중괄호 초기화: { 값1, 값2, 값3 }
- 멤버 순서대로 값 지정
- 일부만 초기화 시 나머지는 0 또는 빈 값
- 동일한 구조체의 객체에 다른 객체의 전체 요소의 값을 복사할 수 있다.
구조체 복사
- 구조체 전체를 한 번에 복사 가능
- product1 = product2; // 모든 멤버가 복사됨
구조체와 메모리
struct ProductInfo {
int num; // 4바이트
char name[100]; // 100바이트
int cost; // 4바이트
};
int main() {
ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
printf("%d\\n", sizeof(myProduct)); // 전체 크기: 108
printf("%d\\n", &myProduct); // 구조체 시작 주소
printf("%d\\n", &myProduct.num); // num의 주소 (= 구조체 시작 주소)
printf("%d\\n", myProduct.name); // name 배열의 주소
printf("%d\\n", &myProduct.cost); // cost의 주소
1000 num 4바이트
1004 name[0~99] 100바이트
1104 cost 4바이트
-----------------------------
총 크기: 108바이트
}
메모리 구조 분석
- 구조체 멤버는 선언 순서대로 메모리에 배치된다.
- 첫 번째 멤버 주소 = 구조체 시작 주소
- 각 멤버는 연속된 메모리 공간 차지
메모리 패딩
struct ProductInfo {
int num; // 4바이트
char name[101]; // 101바이트로 변경
int cost; // 4바이트
1000 num 4바이트
1004 name[0~100] 101바이트
1105 (패딩) 3바이트 <- 자동 추가
1108 cost 4바이트
-----------------------------
총 크기: 112바이트
};
- CPU 성능 최적화를 위한 메모리 정렬
- 컴파일러가 자동으로 빈 공간 추가
- 데이터를 4바이트(또는 8바이트) 경계에 맞춤
- 컴파일러의 설정에 따라 바뀐다.
- 패딩이 발생하는 이유
- CPU는 메모리를 word 단위로 읽음 (32비트: 4바이트)
- 정렬되지 않은 데이터는 여러 번 읽어야 함
- 속도 향상을 위해 메모리 낭비 감수
구조체 변수 가리키기 (포인터)
구조체 포인터의 필요성
- 함수에 구조체 전달 시 효율성 향상
- 동적 메모리 할당 시 필수
- 연결 리스트, 트리 등 자료구조 구현
- 원본 구조체 수정이 필요한 경우
struct ProductInfo {
int num;
char name[100];
int cost;
};
int main() {
ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
ProductInfo* ptr_product = &myProduct; // 구조체 포인터
//*ptr_product; //ProcudtInfo 형이 된다.
// (*포인터).멤버 형식으로 접근
printf("제품 번호 : %d\\n", (*ptr_product).num);
printf("제품 이름 : %s\\n", (*ptr_product).name);
printf("제품 가격 : %d원\\n", (*ptr_product).cost);
//위와 같은 결과
//화살표 연산자로 간단하게 접근
printf("제품 번호 : %d\\n", ptr_product->num);
printf("제품 이름 : %s\\n", ptr_product->name);
printf("제품 가격 : %d원\\n", ptr_product->cost);
}
- → : 포인터 멤버 접근 연산자
- 좌변 : 멤버를 갖고있는 포인터 형 / 우변 : 해당 포인터의 멤버 변수 또는 함수
- ptr_product : 포인터가 가리키는 구조체
- (*ptr_product).num : 구조체의 num 멤버
- 괄호 필수: . 연산자가 보다 우선순위 높음
화살표 연산자의 의미
- ptr->member = (*ptr).member
- 포인터를 통한 멤버 접근 전용 연산자
- 코드 가독성 향상
접근 연산자 정리
- 일반 구조체 변수: . (점)
- 구조체 포인터: > (화살표)
구조체와 함수
struct ProductInfo {
int num;
char name[100];
int cost;
};
void productSwap(ProductInfo* a, ProductInfo* b) {
ProductInfo tmp = *a; // a가 가리키는 구조체를 tmp에 복사
*a = *b; // b의 내용을 a가 가리키는 곳에 복사
*b = tmp; // tmp의 내용을 b가 가리키는 곳에 복사
}
int main() {
ProductInfo myProduct = { 4797283, "제주 한라봉", 19900 };
ProductInfo yourProduct = { 4797284, "딸기 타르트", 10000 };
productSwap(&myProduct, &yourProduct); // 주소 전달
// swap 후: myProduct에는 딸기 타르트 정보가 들어있음
printf("제품 번호 : %d\\n", myProduct.num);
printf("제품 이름 : %s\\n", myProduct.name);
printf("제품 가격 : %d원\\n", myProduct.cost);
}
- 포인터로 받아야 원본 수정이 가능하다.
- 서로 다른 변수지만 같은 주소를 사용하게 된다.
- 구조체 전체 요소를 한번에 넘길 수 있다.
- 임시변수가 필요하다.
- 구조체 크기만큼 메모리를 사용한다.
값 전달 - call by value
struct Time {
int h, m, s; // 시, 분, 초
};
int totalSec(Time t) { // 구조체 전체가 복사됨
return 60 * 60 * t.h + 60 * t.m + t.s;
}
int main() {
Time t = { 1, 22, 48 };
printf("%d\\n", totalSec(t)); // 4968초
}
- 함수 호출 시 구조체 전체 복사
- 원본 데이터 보호 (수정 불가)
- 작은 구조체에 적합
- 매개변수에서 같은 구조체의 변수가 생성되기 때문에 큰 구조체는 오버헤드가 일어날 수 있다.
- 메모리와 시간 소비 (큰 구조체는 비효율적)
주소 전달 - call by address
struct Time {
int h, m, s;
};
int totalSec(Time* t) { // 주소만 전달 (4 또는 8바이트)
return 60 * 60 * t->h + 60 * t->m + t->s;
}
int main() {
Time t = { 1, 22, 48 };
printf("%d\\n", totalSec(&t)); // 4968초
}
- 포인터만 전달 (효율적)
- 원본 수정 가능 (주의 필요)
- 큰 구조체에 필수
- 화살표 연산자로 멤버 접근
값과 주소 선택 기준
- 작은 구조체 + 원본 보호 필요 → 값 전달
- 큰 구조체 → 주소 전달
- 원본 수정 필요 → 주소 전달
- 읽기만 필요 + 큰 구조체 → const 포인터
구조체에 함수 넣기
struct Time {
int h, m, s;
// 구조체 안에 함수 정의 (멤버 함수)
int totalSec() {
return 60 * 60 * h + 60 * m + s; // 멤버 직접 접근
}
};
int main() {
Time t = { 1, 22, 48 };
printf("%d\\n", t.totalSec()); // 객체의 함수 호출
}
- 멤버 함수는 해당 함수가 있는 멤버 변수만 접근할 수 있다.