第1メンバーの規則
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 29/61。
構造体を別の構造体の最初のメンバとして埋め込むと、強力なことが起こります。外側の構造体のメモリアドレスは、その最初のメンバのメモリアドレスと同一になります。これは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)に対しても動作させることができます。メモリレイアウトが完全に一致するため、キャストは安全です。
チャレンジ
簡単シンプルな動物の階層構造を構築することで、最初のメンバールール(first member rule)について学んでいきましょう。ベースとなる Animal 構造体と、そこから派生した Dog 構造体を作成し、Dog へのポインタを安全に Animal へのポインタにキャストできることを実証します。
ここで適用する重要な洞察は、Animal が Dog の最初のメンバーである場合、両方の構造体は同じ開始メモリ番地を共有するということです。これは、ポインタをキャストすることで、Animal* を期待する任意の関数に Dog* を渡すことができることを意味します。
コードは以下の3つのファイルに分けて構成します:
animal.h: インクルードガードを使用して両方の構造体を定義します。Animal構造体には、legs(足の数を表す整数)という単一のフィールドを持たせる必要があります。Dog構造体は、最初のメンバーとしてAnimalを埋め込み、nameフィールド(50文字の文字配列)を追加する必要があります。また、Animal*パラメータを受け取る関数print_animalを宣言します。animal.c:print_animal関数を実装します。この関数は基本型を扱い、Animalについてのみ認識し、その動物が何本の足を持っているかを出力します。main.c:Dog変数を作成し、そのフィールド(埋め込まれた動物の足の数と犬の名前の両方)に値を設定します。次に、Dog*をAnimal*にキャストしてprint_animalに渡すことで、最初のメンバールールを実証します。また、全体像を示すために、犬の名前を別途出力します。
入力として、犬の name(文字列)と number of legs(整数)の2つを受け取ります。
出力は以下のようになるはずです:
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 を呼び出す
// これは「最初のメンバのルール」を実証するものです!
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。