컴퓨터 시스템 : CSAPP 3장 정리 - 3.9 이기종 자료구조
🔷 3.9 이질적 자료구조 (Heterogeneous Data Structures)
C 언어는 서로 다른 타입의 데이터를 하나의 단위로 묶는 두 가지 방법을 제공한다:
- struct (구조체) – 여러 타입의 객체를 연속된 메모리 공간에 저장하여 하나의 단위로 사용
- union (공용체) – 여러 타입의 객체가 동일한 메모리 공간을 공유하도록 함
이러한 구조는 배열과 달리 단일 타입만을 반복하는 것이 아니라, 다양한 타입을 조합할 수 있게 해 준다. 객체지향 언어의 클래스(class)와는 다르지만, 구조체는 객체 정보를 캡슐화하는 데 가장 가까운 C의 수단이다.
🔹 3.9.1 구조체 (Structures)
📌 기본 개념
구조체(struct)는 다양한 타입의 필드를 하나의 단위로 묶는 데이터 타입이다.
struct rec {
int i;
int j;
int a[2];
int *p;
};
- 이 구조체는 총 4개의 필드를 가지고 있다.
- int i (4바이트)
- int j (4바이트)
- int a[2] → 총 8바이트
- int *p (포인터, 8바이트)
- 총 크기: 24바이트
이 필드들은 모두 연속된 메모리 공간에 저장되며, 컴파일러는 각 필드에 대해 바이트 오프셋(byte offset) 정보를 관리한다.
📍 메모리 내 구조체 필드 배치
Offset | 내용
----------------
0 | i
4 | j
8 | a[0]
12 | a[1]
16 | p
즉, 구조체의 시작 주소가 x라면 r->a [1]은 x + 12 위치에 있고, r->p는 x + 16에 있다.
🔧 구조체 필드 접근 방식
컴파일러는 구조체의 필드를 참조할 때 기준 주소 + 오프셋 형태의 주소 계산을 수행한다.
예시: r->i를 r->j로 복사하는 어셈블리
movl (%rdi), %eax ; r->i
movl %eax, 4(%rdi) ; r->j = r->i
- %rdi는 구조체 r의 시작 주소
- i는 오프셋 0 → (%rdi)
- j는 오프셋 4 → 4(%rdi)
🔄 구조체 배열과 내포된 구조체
- 배열이 구조체 내에 포함될 수 있고(int a[2];), 구조체가 또 다른 구조체를 필드로 가질 수도 있다.
- 이런 중첩 구조도 모두 연속된 메모리에 저장되며, 컴파일러는 필드 접근을 위한 오프셋을 정확히 계산한다.
✅ 요약
- 구조체는 서로 다른 타입의 값을 하나의 단위로 묶는 자료형
- 모든 필드는 연속적인 메모리 공간에 배치됨
- 필드 접근은 구조체 주소 + 바이트 오프셋으로 계산
- 어셈블리에서는 오프셋만으로 접근 가능하여 효율적인 코드 생성 가능.
🔷 3.9.2 공용체 (Unions)
📌 공용체란?
공용체(union)는 C 언어에서 여러 타입의 데이터를 하나의 메모리 공간에 겹쳐서 저장할 수 있게 하는 자료형이다.
- 구조체(struct)는 각 필드가 서로 다른 메모리 공간에 저장되지만,
- 공용체는 모든 필드가 동일한 메모리 공간을 공유한다.
📎 문법 예시
union U3 {
char c;
int i[2];
double v;
};
이 공용체는 c, i[2], v 세 가지 필드를 가지며, 모두 동일한 메모리 주소에서 시작한다.
따라서 p->c, p->i[0], p->v는 모두 같은 위치를 참조한다.
🧮 메모리 배치 예시 (비교)
필드 타입 | struct S3 (구조체) | union U3 (공용체) |
char c | offset 0 | offset 0 |
int i[2] | offset 4 | offset 0 |
double v | offset 16 | offset 0 |
총 크기 | 24 bytes | 8 bytes |
공용체는 가장 큰 필드의 크기만큼의 공간만 확보한다.
🔧 C와 어셈블리 코드 비교 예제
구조체 node_s와 공용체 node_u 비교
구조체 방식:
struct node_s {
struct node_s *left;
struct node_s *right;
double data[2];
};
- 내부 노드: left, right 사용 (16B)
- 리프 노드: data[2] 사용 (16B)
- 항상 32B 차지 → 절반 낭비
공용체 방식:
union node_u {
struct {
union node_u *left;
union node_u *right;
} internal;
double data[2];
};
- left/right와 data는 서로 겹쳐서 저장
- 모든 노드가 16B만 사용 → 공간 절약
사용 예:
n->data[0]; // 리프 노드 접근
n->internal.left; // 내부 노드 접근
연습문제 3.43
📐 어셈블리 코드 예시 (Practice Problem 3.43)
typedef union {
struct {
long u;
short v;
char w;
} t1;
struct {
int a[2];
char *p;
} t2;
} u_type;
expr: up->t1.v
movw 8(%rdi), %ax
movw %ax, (%rsi)
- %rdi: up (u_type 포인터)
- t1.v: 오프셋 8에 존재
- %rsi: 결과 저장 위치
expr: up->t2.a[up->t1.u]
movq (%rdi), %rax ; t1.u
movl (%rdi,%rax,4), %eax ; t2.a[...]
movl %eax, (%rsi)
⚠️ 주의: 공용체는 타입 시스템을 우회하므로 위험할 수 있음
- 어떤 필드에 값을 쓰고, 다른 타입으로 해석하면 의미가 달라짐
- 하드웨어 레벨 bit 패턴을 직접 조작할 때는 유용하지만, 논리적 오류 발생 가능성 있음
✅ 요약
- 공용체는 하나의 메모리 공간을 여러 타입으로 공유하는 구조
- 가장 큰 필드의 크기만큼만 공간 할당
- 필드 접근 시 오프셋은 항상 0
- C에서는 구조가 같아도 의미가 다를 수 있으므로 사용에 주의 필요
- 공간 절약에는 효과적이지만, 타입 안정성을 해침
🔷 3.9.3 데이터 정렬 (Data Alignment)
📌 정렬의 개념
많은 컴퓨터 시스템에서는 데이터의 주소가 특정 바이트 경계에 맞춰져야 한다는 제약이 있다.
- 예: 어떤 타입의 데이터는 2, 4, 8 바이트 정렬을 요구함
- 이를 정렬(alignment)이라 부르며, 하드웨어 설계와 메모리 접근 성능을 향상시키기 위해 사용된다
정렬 요구사항 예
데이터 타입 | 요구 정렬 바이트 수 (K) |
char | 1 |
short | 2 |
int, float | 4 |
long, double, 포인터 | 8 |
📐 예제: 구조체 정렬 문제
문제 구조체
struct S1 {
int i;
char c;
int j;
};
가능한 정렬 (비효율적)
Offset Contents
0 i
4 c
5~7 ??? (패딩 필요)
8 j
- j는 4바이트 정렬을 필요로 하지만, c 다음이면 offset 5 → 정렬 위반
- 해결: c 다음에 3바이트 패딩 삽입
결과 구조
- 전체 크기: 12바이트
- 정렬 만족: i at 0, j at 8 → 모두 4바이트 배수
🔁 구조체 배열에서의 정렬
struct S2 {
int i;
int j;
char c;
};
struct S2 d[4];
- S2의 크기가 9바이트라면:
- 배열 원소는 xd, xd+9, xd+18,... 주소에 위치
- 이 경우, i, j는 4바이트 정렬 조건 위반 발생 가능
해결: 구조체 크기를 12바이트로 패딩 → 배열 원소 정렬 유지
🔧 어셈블리에서의 정렬 지시자
컴파일러는 전역 데이터에서 .align 명령어를 통해 정렬을 강제한다:
.align 8
- 이 명령어는 다음에 나오는 데이터가 8바이트 경계에서 시작되도록 함
⚠️ 정렬 위반 시의 문제점
- 대부분의 x86-64 시스템은 정렬이 안 맞아도 동작은 함
- 하지만:
- 성능 저하 발생
- 일부 명령어(SSE 등)는 정렬 위반 시 오류 발생
예: SSE 명령은 16바이트 정렬 요구
→ 메모리 할당, 스택 프레임 모두 16바이트 정렬 필요
✅ 요약
- 정렬은 특정 자료형이 특정 주소 기준으로 저장되어야 하는 제약
- 정렬을 맞추기 위해 컴파일러는 패딩을 삽입
- 구조체의 시작 주소 및 내부 필드들은 각자의 정렬 조건을 만족해야 함
- 배열로 사용될 경우, 구조체 크기도 정렬 조건을 만족해야 함
- 어셈블리에서는 .align 등으로 정렬 지시를 명시함
✅ 3.9 전체 요약
📌 핵심 요점 정리
3.9.1 구조체 | 서로 다른 타입을 하나로 묶어 연속된 메모리 공간에 저장. 각 필드는 고정된 바이트 오프셋을 가짐 |
3.9.2 공용체 | 여러 타입이 하나의 메모리 공간을 공유. 가장 큰 필드의 크기만큼만 메모리 사용. 주소는 항상 0부터 시작 |
3.9.3 데이터 정렬 | 성능 향상과 하드웨어 호환성을 위한 메모리 정렬 필수. 구조체는 필드 사이와 끝에 패딩 추가 가능 |
🧠 한 줄 요약
"C의 구조체와 공용체는 다양한 타입의 데이터를 메모리에 효율적으로 표현할 수 있도록 하며, 하드웨어가 요구하는 정렬 조건을 만족시키기 위해 패딩과 오프셋 계산이 중요하다."