閉じる

C言語の型に別名を付ける(typedef) 基本の書き方と使い方

C言語のtypedefは、既存の型に読みやすい別名を付けて、コードの見通しや移植性を高めるための仕組みです。

本記事では初心者の方がつまずきやすい*の結び付き新しい型は生まれないという重要ポイントを押さえつつ、整数型やポインタ型、structと組み合わせる実例まで、基本から丁寧に解説します。

typedefとは

型に別名を付ける意味

typedefは、既存の型に別名を与えるためのキーワードです。

別名をつけることで、意図が伝わる型名を作れます。

たとえばunsigned longのように長い宣言をusizeなどに置き換えると、コードの可読性が高まります。

特定のプラットフォームに依存する型表現を隠蔽し、将来的に実装を差し替えるときにも宣言の変更範囲を最小化できます。

使うメリット

読みやすさ変更に強い設計が大きな利点です。

ポインタや関数ポインタなど複雑な宣言を分かりやすく表現でき、APIの表面から実装詳細を隠せます。

さらに、プラットフォーム差異を吸収できるため、32bit/64bitの違いを意識するコードを減らせます。

新しい型は作らない

重要なのは、typedef新しい型を作りません

あくまで既存の型に別名を付けるだけです。

たとえばtypedef int Meter;typedef int Second;は、コンパイラにとってどちらもintであり、相互代入も可能です。

型安全性を高めたい場合はstructや別の設計を検討します。

typedefの書き方と使い方

typedefの基本構文

基本構文はtypedef 既存の型 別名;です。

読み下すコツは、最後の識別子が新しい名前だと意識することです。

C言語
// 基本構文の例
typedef unsigned long usize;  // unsigned long に usize という別名を付ける
typedef const char* cstr;     // const char* に cstr という別名を付ける

実行例はありません

このセクションは宣言のみのため出力はありません。

整数型のtypedef例

整数型はstdint.hの固定幅整数を使うのが基本ですが、既存プロジェクトでの命名統一などの目的で別名を付けることがあります。

C言語
#include <stdio.h>
#include <stdint.h>

// わかりやすい別名の例
typedef int32_t  i32;
typedef uint64_t u64;

int main(void) {
    i32 a = -42;           // int32_t と同じ
    u64 b = 10000000000ULL; // uint64_t と同じ

    printf("a=%d (sizeof(i32)=%zu)\n", a, sizeof(i32));
    printf("b=%llu (sizeof(u64)=%zu)\n", (unsigned long long)b, sizeof(u64));
    return 0;
}
実行結果
a=-42 (sizeof(i32)=4)
b=10000000000 (sizeof(u64)=8)

ここでは固定幅整数の型名に短い別名を与え、読みやすさを高めています。

新しい数値型が生まれているわけではありません

ポインタ型のtypedef例

ポインタは宣言が読みにくくなりがちです。

typedefで別名を与えると見通しが良くなります。

ただし*の結び付きconstの適用対象に注意します。

C言語
#include <stdio.h>

// int* の別名。PInt という「intへのポインタ型」を表す型名を作る
typedef int* PInt;

int main(void) {
    int x = 10;
    int y = 20;

    PInt px = &x;  // px は int* 型
    PInt py = &y;  // py も int* 型。typedef を使うと "両方ポインタ" になる

    // const の付き方に注意: "const PInt" は "int* const"
    // つまり「ポインタ自体が定数」であり、「指す先が const」ではありません。
    const PInt cpx = &x; // cpx は「constなポインタ」(先は変更可)
    *cpx = 123;          // OK: 先の値は書き換え可能
    // cpx = &y;         // NG: ポインタを書き換えることはできない(コンパイルエラー)

    printf("*px=%d, *py=%d, *cpx=%d\n", *px, *py, *cpx);
    return 0;
}
実行結果
*px=10, *py=20, *cpx=123

大事なコツは、typedef名は「ひとつの型名」として振る舞うことです。

const PIntint* constと同じであり、constが「ポインタそのもの」にかかる点に注意してください。

先をconstにしたいならtypedef const int* PCInt;のように宣言します。

typedefのスコープ

typedefで作った別名にもスコープがあります。

ファイルスコープで定義すればファイル全体で使え、ブロックスコープで定義すればそのブロックの中だけで有効です。

同名のtypedefを内側で再定義すると、外側を隠します。

C言語
#include <stdio.h>

// ファイルスコープの別名
typedef int Length;

int main(void) {
    printf("outer sizeof(Length)=%zu\n", sizeof(Length)); // int のサイズ

    {
        // ブロック内での別名の再定義(外側を隠す)
        typedef double Length;
        printf("inner sizeof(Length)=%zu\n", sizeof(Length)); // double のサイズ
    }

    printf("outer sizeof(Length)=%zu\n", sizeof(Length)); // 再び外側の定義に戻る
    return 0;
}
実行結果
outer sizeof(Length)=4
inner sizeof(Length)=8
outer sizeof(Length)=4

スコープを意識して同名の再定義は慎重に扱いましょう。

読み手が混乱しやすくなります。

typedefの命名のコツ

初心者のうちは「型の意味が伝わる短い名前」を心がけるのが良いです。

特にポインタ型はPtrを末尾に付けるなど、ポインタであることがわかる名前が安全です。

標準やPOSIXが使うsize_tのような_tで終わる名前は広く見かけますが、自作の公開APIでは末尾_tを避けるのが無難です(将来の衝突や規約違反を避けるため)。

以下は例です。

用途と方針備考
ポインタであることを明示PInt, BufferPtr, NodePtr読む人が即座にポインタとわかる
意味を表す短縮形usize, index_tではなくidxやindex_type末尾_tは公開APIでは避けると安全
固定幅整数の短名i32, u64stdint.hを前提にすると明確
コンテキストを表すFileHandle, SocketIdOS依存の実体を隠すのに便利

structとtypedefの使い方

無名structに別名を付ける

typedefstructと組み合わせると、タグ名を書かずに簡潔に型名を作れます。

C言語
#include <stdio.h>

// 無名structに Point という別名を付ける
typedef struct {
    int x;
    int y;
} Point;

int main(void) {
    Point p = { .x = 3, .y = 4 };
    printf("Point(%d, %d)\n", p.x, p.y);
    return 0;
}
実行結果
Point(3, 4)

この形だとstructキーワードを書かずにPointだけで扱えるため、宣言と使用が簡潔になります。

structタグ名とtypedef名

タグ名とtypedef名を両方使うと、前方宣言が必要なときに便利です。

典型例は自己参照するリストのノードです。

C言語
#include <stdio.h>

// "Node" というタグ名の前方宣言と、同名の typedef を用意
typedef struct Node Node;

struct Node {
    int value;
    Node* next; // ここで Node* を使えるのが前方宣言の利点
};

void print_list(const Node* head) {
    for (const Node* cur = head; cur != NULL; cur = cur->next) {
        printf("%d", cur->value);
        if (cur->next) printf("->");
    }
    printf("\n");
}

int main(void) {
    // 3 -> 2 -> 1 のリストを作る
    Node n1 = { .value = 1, .next = NULL };
    Node n2 = { .value = 2, .next = &n1 };
    Node n3 = { .value = 3, .next = &n2 };

    print_list(&n3);
    return 0;
}
実行結果
3->2->1

タグ名(struct Node)と別名(Node)は別の仕組みです。

両方定義しておくと、struct Node表記とNode表記のどちらも選べます。

宣言と使用の基本

  • typedef struct { ... } Point;のように無名structを使った場合、変数宣言はPoint p;と書きます。
  • struct Point { ... };とだけ書いた場合はstruct Point p;が必要です。typedef struct Point Point;を加えるとPoint p;だけで済みます。
C言語
// 使い分けの短い例(出力はありません)
struct Raw { int a; };         // これだけだと struct Raw 型
typedef struct Raw Raw;         // Raw という別名を付ける

typedef struct { int x, y; } Point; // 無名structに別名

// 使い方例
// struct Raw r; // OK
// Raw r2;       // OK (typedefのおかげ)
// Point p;      // OK (typedefのおかげ)

実行例はありません

このセクションは宣言のみのため出力はありません。

初心者がやりがちなミスと注意点

*の結び付きに注意

Cの宣言では*は変数名に結び付きます。

つまり「星は変数に付く」と覚えるのが安全です。

C言語
#include <stdio.h>

int main(void) {
    int  a = 0;
    int* p, q;  // p は int*、q は int (ここが落とし穴)

    // わかりにくさを可視化する
    printf("sizeof(p)=%zu (pointer), sizeof(q)=%zu (int)\n",
           sizeof(p), sizeof(q));

    // 対策1: 変数ごとに * を付ける
    int *p2, *q2;

    // 対策2: typedefで「ポインタ型の別名」を作る
    typedef int* PInt;
    PInt p3, q3; // p3 も q3 も int* になる

    printf("sizeof(p3)=%zu, sizeof(q3)=%zu (both pointer)\n",
           sizeof(p3), sizeof(q3));
    return 0;
}
実行結果
sizeof(p)=8 (pointer), sizeof(q)=4 (int)
sizeof(p3)=8, sizeof(q3)=8 (both pointer)

混乱を避けるにはtypedefでポインタ型に別名を付けるか、各変数にそれぞれ*を明示しましょう。

さらにconstの付き方にも注意が必要です。

const intは「先がconst」、int constは「ポインタがconst」です。

typedef名を使うと全体がひとかたまりになるため、constの意味が変わります。

多重定義を避ける

同じtypedefをヘッダから何度も取り込むと、コンパイラや設定によっては警告が出ることがあります。

インクルードガード#pragma onceで防ぎましょう。

C言語
/* types.h */
#ifndef TYPES_H_
#define TYPES_H_

#include <stdint.h>

// プロジェクト全体で共有する別名
typedef uint32_t UserId;
typedef const char* CString;

#endif /* TYPES_H_ */
C言語
/* main.c */
#include <stdio.h>
#include "types.h"
#include "types.h" // 誤って二重にインクルードしても、ガードがあれば安全

int main(void) {
    UserId id = 42;
    CString name = "alice";
    printf("id=%u, name=%s\n", id, name);
    return 0;
}
実行結果
id=42, name=alice

同一スコープでtypedefをまったく同じ内容で繰り返すこと自体は許容される処理系もありますが、警告や可読性の低下につながるため避けるのが実務的です。

typedefの使いすぎに注意

隠しすぎは可読性を下げます

とくに配列やポインタをtypedefで隠すと、実体が配列なのかポインタなのかがコードから見えにくくなります。

次のような指針を参考にするとバランスが取りやすいです。

  • 「意味のある抽象化」や「プラットフォーム差異の隠蔽」には有効です。
  • 「ただ短くしたいだけ」の別名や、ポインタ性を隠すためだけの別名は慎重に。
  • 公開APIでは_tで終わる名前は避け、衝突や誤解を防ぎます。
良い例説明
typedef const char* CString;文字列参照の意図が明確。プラットフォーム差異を吸収しやすい
typedef struct { int x, y; } Point;データ構造の名前付けで読みやすさ向上
typedef int* BufferPtr;ポインタ性を明示しつつ短縮
避けたい例説明
typedef int I;意味が乏しく、intと同じ可読性
typedef int* P;用途が不明確。ポインタ先の意味も伝わらない
typedef int ARR10[10];配列である事実が隠れて誤用を招きやすい

まとめ

typedefは既存の型に別名を付けるだけで、新しい型は作りません

しかし、その「ただの別名」がコードの読みやすさや保守性、移植性を大きく高めます。

整数型やポインタ型、structと組み合わせる基本パターンを押さえ、*の結び付きconstの適用対象に細心の注意を払いましょう。

命名は意味が伝わることを最優先にし、インクルードガードで多重定義の問題を避けつつ、隠しすぎないバランスを大切にしてください。

これらのポイントを意識すれば、初心者の方でもtypedefを安全かつ効果的に使いこなせます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!