Menu

C言語の typedef:型に名前を付ける、typedef struct の定石、使わないほうがよい場面

typedef が既存のC言語の型に新しい名前を与える仕組み - タグ付き・タグなしの typedef struct の定石、関数ポインタと配列のための typedef、そして typedef が害になる唯一の場合:ポインタを隠すこと。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

typedef がすることは1つだけ、すでに存在する型に別の名前を与えることです。新しい型は作られず、メモリも確保されず、値の振る舞いについて何も変わりません。名前付けの道具であり、そして組み込みの型名が長くなりがちなC言語(unsigned long longstruct Employeevoid (*)(int))では、良い名前には大きな価値があります。

基本の形

構文は、先頭に typedef を取り付けた変数宣言のように読めます。変数名が来るはずの場所に、新しい型名を置くのです。

unsigned long  count;    // count という名前の変数を宣言
typedef unsigned long  ulong;   // ulong という名前の「型」を宣言

これが規則のすべてで、このページの後半に出てくる奇妙に見えるものも含め、今後読むあらゆる typedef を説明してくれます。

CelsiusFahrenheit の組が買ってくれないものに注意してください。型の安全性です。どちらも単なる double なので、摂氏が期待される場所へ華氏の値を渡しても平然とコンパイルされます。これらの名前は人間のために意図を説明するもので、検査を作り出すものではありません。

typedef struct の定石

ほとんどのCプログラムに typedef が含まれる理由がこれです。Cでは構造体の型名にキーワードが含まれます。

struct Point { int x; int y; };
struct Point p;              /* 毎回「struct」という語が付く */

typedef はそれを畳み込みます。

さきほどの規則で読んでみましょう。typedef を剥がすと struct Point {...} Point; になり、これは Point という名前の変数の宣言です。typedef を戻すと、Point は代わりに型名になります。

タグを付けるか付けないか

タグをまったく省いて、typedef の名前だけを持つ無名の構造体にもできます。

typedef struct {
    int x;
    int y;
} Point;          /* 「struct Point」は存在しない - 「Point」だけ */

こちらのほうが整っていますし、単なるデータのレコードならこれがよくある流儀です。しかし構造体が自分自身に言及する必要が生じた瞬間に破綻します。typedef の名前は、自分自身の宣言が終わるまで使えないからです。

/* コンパイルできない: 自分の波かっこの中では「Node」はまだ型ではない。 */
typedef struct {
    int value;
    Node *next;
} Node;

タグを残せば自己参照は機能します。struct Node はタグが見えた時点で使えるようになるからです。

実用的な規則です。タグは残す。 かかるのは一語ぶんで、typedef の名前と一致するので紛らわしくもなく、自己参照とヘッダーでの前方宣言への扉を開いたままにしてくれます。その連結ノードが何に育つかは構造体とポインタを参照してください。

前方宣言と不透明型

構造体の中身が分からなくても構造体へのポインタの大きさは既知なので、ヘッダーはメンバを明かさずに型を配ることができます。

/* stack.h */
typedef struct Stack Stack;     /* 宣言であって定義ではない */

Stack *stack_create(void);
void   stack_push(Stack *s, int value);
int    stack_pop(Stack *s);
void   stack_destroy(Stack *s);

完全な struct Stack { ... };stack.c にあり、外の誰もそのメンバに触れません。この不透明型のパターンが、Cのライブラリがカプセル化を実現する方法であり、標準ライブラリの FILE がまさにそれです - fopen から FILE * を受け取りますが、その中身については何も教えてもらえません。ファイル操作を参照してください。

enum と union のための typedef

同じ定石が、残る2つの複合型にも当てはまります。

typedef がなければこれらは enum Color c;union Number n; になります。実際に何をするものかは列挙型と共用体を参照してください。

関数ポインタのための typedef

ここで typedef は便利さにとどまらず、ほぼ必要不可欠なものになります。関数ポインタの生の構文は読みにくさで有名です。

void (*handler)(int, const char *);        /* 変数 */
void register_cb(void (*cb)(int, const char *));   /* 引数 */

型に一度名前を付ければ、どちらも読めるようになります。

もう一度 typedef を剥がす規則を当てはめましょう。int (*IntBinaryOp)(int, int); だけなら、2つの int を取り int を返す関数を指す IntBinaryOp という変数の宣言です。typedef を付けると、それが型の名前になります。詳しくは関数ポインタにあります。

typedef と #define

単純な別名としては入れ替え可能に見えますが、そうではありません。

#define INT_PTR int *
typedef int *int_ptr;

INT_PTR  a, b;    /* 展開されると: int *a, b;  -- b はただの int! */
int_ptr  c, d;    /* c も d も int *                */

#define はコンパイラがコードを見る前に行われる盲目的なテキストの置換で、typedef はコンパイラが理解する本物の宣言であり、並びの中のすべての名前に適用されます。型には typedef を使い、#define は定数とマクロのために取っておきましょう。

typedef が害になるとき

typedef は読み手が必要とする情報を隠すこともできます。最も鋭い例がポインタを隠すことです。

typedef struct Node *NodeRef;    /* 星が消える */

NodeRef n = get_node();

NodeRef n を見ても、読み手は nNULL になりうるのか、解放しなければならないのか、n とそのコピーが同じオブジェクトを共有するのか、メンバに . で届くのか -> で届くのかを判断できません。この4つの問いはすべて struct Node *n なら即座に答えられます。

標準ライブラリも同意見です。構造体を typedef し(FILE)、使う場所に星を残しています(FILE *fp)。そこから2つの妥当な指針が導かれます。

  • 構造体、共用体、列挙型、関数ポインタは typedef する - 取り除かれる雑音は何の情報も持っていません。
  • 短くするためだけにポインタを typedef しないし、基本型を偽装するために typedef もしないこと(typedef int Boolean; は誰かがそこに 7 を入れたくなるよう誘います - 代わりに <stdbool.h>bool を使いましょう。真偽値で扱っています)。

<stdint.h> にある標準ライブラリ自身の固定幅の名前(uint32_tint64_tsize_t)はすべて typedef であり、この機能を擁護する最良の論拠です。自分が何であるかを正確に述べており、可搬で、何も隠していません。

よくある質問

C言語の typedef は何をしますか?

既存の型に2つ目の名前を与えます。typedef unsigned long ulong; は、それ以降 ulongunsigned long が同じ型であることを意味します。新しい型も新しい記憶領域も作りません - すでにある型を綴る、より短いか説明的な方法を作るだけです。

C言語の typedef struct の定石とは何ですか?

typedef struct Point { int x; int y; } Point; は構造体の宣言と Point という型名の命名を1つの文で行うので、struct Point p; の代わりに Point p; と書けます。連結リストのノードのように構造体が自分自身を参照する必要があるときは、タグ(struct Point)を残しておくことが重要です。

typedef と #define の違いは何ですか?

typedef はコンパイラが扱い本物の型の別名を作ります。#define はコンパイル前にプリプロセッサが行うテキストの置換です。この違いはポインタで噛みつきます。#define PTR int* だと PTR a, b;int* a, b; に展開され、ポインタなのは a だけです。typedef int *PTR; なら両方ポインタになります。

C言語でポインタ型を typedef すべきですか?

たいていの場合すべきではありません。typedef struct Node *NodeRef; はその型がポインタであるという事実を隠すので、読み手はその変数が NULL になりうるのか、解放が必要なのか、-> が要るのかを判断できません。標準ライブラリの FILE * がまさにこの理由で星を見えるままにしています。構造体を typedef して、それへのポインタは typedef しないこと。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める