Menu
Coddy logo textTech

첫 번째 멤버 규칙

Coddy C 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 29번째.

구조체를 다른 구조체의 첫 번째 멤버로 포함할 때, 강력한 일이 일어납니다. 외부 구조체의 메모리 주소는 첫 번째 멤버의 메모리 주소와 동일합니다. 이는 C 표준에 의해 보장됩니다.

이전 예제를 살펴보겠습니다:

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // 첫 번째 멤버
    int grade;
} Child;

Child 변수를 생성할 때, Parent 멤버는 Child 메모리의 맨 처음에 위치합니다. 이는 Child에 대한 포인터가 해당 Parent 멤버에 대한 포인터와 정확히 동일한 주소를 가리킨다는 것을 의미합니다.

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // 안전한 형변환!

두 포인터는 동일한 주소를 가집니다. 이를 통해 Parent*를 기대하는 모든 함수에 Child*를 전달할 수 있습니다:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

// main에서:
Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // 완벽하게 동작합니다

이 기법은 C 언어에서 상속의 기초가 됩니다. 베이스 타입(base type)을 위해 작성된 함수는 베이스가 먼저 포함되어 있는 한 어떠한 파생 타입(derived type)에서도 작동할 수 있습니다. 메모리 레이아웃이 완벽하게 일치하기 때문에 캐스트(cast)는 안전합니다.

challenge icon

챌린지

쉬움

간단한 동물 계층 구조를 구축하여 첫 번째 멤버 규칙(first member rule)을 살펴보겠습니다. 기본 Animal 구조체와 파생된 Dog 구조체를 생성한 다음, Dog에 대한 포인터가 어떻게 Animal에 대한 포인터로 안전하게 캐스팅될 수 있는지 시연해 보겠습니다.

적용할 핵심 통찰은 다음과 같습니다. AnimalDog의 첫 번째 멤버일 때, 두 구조체는 동일한 시작 메모리 주소를 공유합니다. 즉, 포인터를 캐스팅함으로써 Animal*를 기대하는 모든 함수에 Dog*를 전달할 수 있습니다.

코드는 세 개의 파일로 구성됩니다.

  • animal.h: 인클루드 가드(include guards)와 함께 두 구조체를 정의합니다. Animal 구조체는 단일 필드인 legs(다리 수를 나타내는 정수)를 가져야 합니다. Dog 구조체는 Animal첫 번째 멤버로 포함하고 name 필드(50자의 문자 배열)를 추가해야 합니다. 또한 Animal* 매개변수를 받는 print_animal 함수를 선언합니다.
  • animal.c: print_animal 함수를 구현합니다. 이 함수는 기본 타입과 함께 작동하며, Animal에 대해서만 알고 동물의 다리 수를 출력합니다.
  • main.c: Dog 변수를 생성하고 해당 필드(포함된 동물의 다리와 개의 이름 모두)를 채웁니다. 그런 다음 Dog*Animal*로 캐스팅하고 이를 print_animal에 전달하여 첫 번째 멤버 규칙을 시연합니다. 또한 전체적인 모습을 보여주기 위해 개의 이름을 별도로 출력합니다.

두 개의 입력을 받게 됩니다: 개의 이름(문자열)과 다리 수(정수).

출력은 다음과 같아야 합니다:

Dog: Buddy
Legs: 4

여기서 Buddy는 개의 이름이고 4는 다리 수입니다. main.c에서 개의 이름을 먼저 출력한 다음, 캐스팅된 포인터로 print_animal을 호출하여 다리 수를 표시하세요.

이것은 기본 타입(Animal)을 위해 작성된 함수가 포인터 캐스팅을 통해 파생 타입(Dog)과 어떻게 원활하게 작동할 수 있는지를 보여줍니다. 이는 C 언어에서 상속의 기초가 됩니다.

치트 시트

구조체가 다른 구조체의 첫 번째 멤버로 포함될 때, 두 구조체는 동일한 시작 메모리 주소를 공유합니다. 이는 C 표준에 의해 보장됩니다.

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // 첫 번째 멤버
    int grade;
} Child;

외부 구조체에 대한 포인터는 첫 번째 멤버에 대한 포인터로 안전하게 캐스팅될 수 있습니다:

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // 안전한 캐스팅 - 동일한 주소

이를 통해 기본 타입을 기대하는 함수에 파생 타입 포인터를 전달할 수 있습니다:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // 메모리 레이아웃이 일치하므로 작동함

이 기법은 C에서 상속의 기초가 되며, 기본 타입을 위해 작성된 함수가 파생 타입에서도 작동할 수 있게 합니다.

직접 해보기

#include <stdio.h>
#include <string.h>
#include "animal.h"

int main() {
    // 입력 읽기
    char name[50];
    int legs;
    scanf("%s", name);
    scanf("%d", &legs);
    
    // TODO: Dog 변수 생성
    
    // TODO: strcpy를 사용하여 개의 이름 설정
    
    // TODO: 다리 개수 설정 (포함된 Animal을 통해 접근)
    
    // TODO: "Dog: <name>" 형식으로 개의 이름 출력
    
    // TODO: Dog*를 Animal*로 캐스팅하고 print_animal 호출
    // 이것은 첫 번째 멤버 규칙(first member rule)을 보여줍니다!
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨