크래프톤 정글 (컴퓨터 시스템: CSAPP)/3장 프로그램의 기계수준 표현

컴퓨터 시스템 : CSAPP 3장 정리 - 3.9 이기종 자료구조

고웅 2025. 4. 7. 08:26

🔷 3.9 이질적 자료구조 (Heterogeneous Data Structures)

C 언어는 서로 다른 타입의 데이터를 하나의 단위로 묶는 두 가지 방법을 제공한다:

  1. struct (구조체) – 여러 타입의 객체를 연속된 메모리 공간에 저장하여 하나의 단위로 사용
  2. 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의 구조체와 공용체는 다양한 타입의 데이터를 메모리에 효율적으로 표현할 수 있도록 하며, 하드웨어가 요구하는 정렬 조건을 만족시키기 위해 패딩과 오프셋 계산이 중요하다."