C言語のtypedef
は、既存の型に読みやすい別名を付けて、コードの見通しや移植性を高めるための仕組みです。
本記事では初心者の方がつまずきやすい*の結び付きや新しい型は生まれないという重要ポイントを押さえつつ、整数型やポインタ型、structと組み合わせる実例まで、基本から丁寧に解説します。
typedefとは
型に別名を付ける意味
typedef
は、既存の型に別名を与えるためのキーワードです。
別名をつけることで、意図が伝わる型名を作れます。
たとえばunsigned long
のように長い宣言をusize
などに置き換えると、コードの可読性が高まります。
特定のプラットフォームに依存する型表現を隠蔽し、将来的に実装を差し替えるときにも宣言の変更範囲を最小化できます。
使うメリット
読みやすさと変更に強い設計が大きな利点です。
ポインタや関数ポインタなど複雑な宣言を分かりやすく表現でき、APIの表面から実装詳細を隠せます。
さらに、プラットフォーム差異を吸収できるため、32bit/64bitの違いを意識するコードを減らせます。
新しい型は作らない
重要なのは、typedef
は新しい型を作りません。
あくまで既存の型に別名を付けるだけです。
たとえばtypedef int Meter;
とtypedef int Second;
は、コンパイラにとってどちらもint
であり、相互代入も可能です。
型安全性を高めたい場合はstruct
や別の設計を検討します。
typedefの書き方と使い方
typedefの基本構文
基本構文はtypedef 既存の型 別名;
です。
読み下すコツは、最後の識別子が新しい名前だと意識することです。
// 基本構文の例
typedef unsigned long usize; // unsigned long に usize という別名を付ける
typedef const char* cstr; // const char* に cstr という別名を付ける
実行例はありません
このセクションは宣言のみのため出力はありません。
整数型のtypedef例
整数型はstdint.h
の固定幅整数を使うのが基本ですが、既存プロジェクトでの命名統一などの目的で別名を付けることがあります。
#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
の適用対象に注意します。
#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 PInt
はint* const
と同じであり、const
が「ポインタそのもの」にかかる点に注意してください。
先をconst
にしたいならtypedef const int* PCInt;
のように宣言します。
typedefのスコープ
typedef
で作った別名にもスコープがあります。
ファイルスコープで定義すればファイル全体で使え、ブロックスコープで定義すればそのブロックの中だけで有効です。
同名のtypedef
を内側で再定義すると、外側を隠します。
#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, u64 | stdint.h を前提にすると明確 |
コンテキストを表す | FileHandle, SocketId | OS依存の実体を隠すのに便利 |
structとtypedefの使い方
無名structに別名を付ける
typedef
はstruct
と組み合わせると、タグ名を書かずに簡潔に型名を作れます。
#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
名を両方使うと、前方宣言が必要なときに便利です。
典型例は自己参照するリストのノードです。
#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;
だけで済みます。
// 使い分けの短い例(出力はありません)
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の宣言では*
は変数名に結び付きます。
つまり「星は変数に付く」と覚えるのが安全です。
#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
で防ぎましょう。
/* types.h */
#ifndef TYPES_H_
#define TYPES_H_
#include <stdint.h>
// プロジェクト全体で共有する別名
typedef uint32_t UserId;
typedef const char* CString;
#endif /* TYPES_H_ */
/* 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
を安全かつ効果的に使いこなせます。