
C言語基礎|ポインタの型
ポインタは「住所」だけじゃない!型が付くことで“どれだけ・どう扱うか”が決まる。
ポインタって「番地(アドレス)を入れるもの」…ここまではOKですよね。
でもC言語のポインタは、ただの番地では終わりません。
Cではポインタに 型 が付いていて、これがめちゃくちゃ重要です。
なぜなら、その型が
- どんなオブジェクトを指すつもりなのか
- どれくらいの大きさ(バイト数)を、ひとまとまりとして扱うのか
を決めてしまうからです。
型を間違えると、scanf で変数の外まで書き込んだり、swap で本来より大きい領域を交換してしまったりして、かなり危険なことが起きます。
ここでは「ポインタの型」が安全性に直結する理由を、図と表でしっかり整理していきますね。
スカラ型(scalar type)って何?
番地を表すポインタは「数っぽい量」とみなせます。
C言語では、算術型(int や double など)と、ポインタ型をまとめて スカラ型 と呼びます。
スカラ型のイメージ
| 分類 | 例 | 特徴 |
|---|---|---|
| 算術型 | int, double | 数として計算できる |
| ポインタ型 | int *, char * | 番地という「量」を表す(数っぽい) |
scalar は「大きさはあるけど方向はない量」という意味で、vector(向きがある量)と対比されます。
この話は「ポインタも値として変数に入る」ことを再確認するための導入だと思うとちょうどいいです。
ポインタの型とは(Type * が意味すること)
ここが本題です。
Type * 型ポインタは、単に「○○番地」を指すのではなく、
○○番地を先頭に格納された Type 型オブジェクト
を指すものとして扱われます。
ポインタ型が“何を指すか”を決める
| ポインタ型 | 指す対象のイメージ | *p で得られる型 |
|---|---|---|
| int * | int 型オブジェクト | int |
| double * | double 型オブジェクト | double |
| char * | char 型オブジェクト | char |
つまり「住所+型情報のセット」みたいなものです。
型を間違えると何が起きる?(安全性の話)
例1:scanf の指定と変数の型が違うと危険
scanf は、指定された変換指定子に合わせた型の値を、渡された住所に書き込みます。
サンプルプログラム
short に long を読み込ませようとするミスのあるプログラム例です。(わざと危ない例です)。
メッセージも別の日本語に変更しています。
注意:この例は「やってはいけない例」を理解するためのものです。実際に真似しないでね、という意図です。
#include <stdio.h>
int main(void)
{
short s;
puts("整数を入力してください(危険な例の説明用)。");
printf("number = ");
// 本来は %hd と &s を組み合わせるべきなのに、わざと間違える
scanf("%ld", &s);
printf("入力後の s = %hd\n", s);
return 0;
}何が危険なの?(図でバイト数のズレを見る)
環境によって大きさは違いますが、例えばこういうケースを想像してください。
- short が 2バイト
- long が 8バイト
scanf("%ld", …) は long を読み取って 8バイト書き込もうとします。
でも &s が指す先は 2バイト分しか用意していないので、はみ出して書き込む可能性があります。
図:領域を超えて書き込まれるイメージ

図の説明
- 左の2バイトが本来の s の箱
- scanf が要求する型(long)に合わせて書く範囲が決まる
- だから「型の指定」と「変数の型」が一致しないと危険
scanf とポインタ型の関係(書式を整理)
scanf は「変換指定子」と「格納先ポインタの型」を対応させる必要があります。
代表的な scanf の対応(よく使うもの)
| 読み込みたい型 | 変換指定子 | 渡すべきもの |
|---|---|---|
| int | %d | int 変数のアドレス(例:&i) |
| long | %ld | long 変数のアドレス(例:&l) |
| short | %hd | short 変数のアドレス(例:&s) |
| double | %lf | double 変数のアドレス(例:&x) |
| char(1文字) | %c | char 変数のアドレス(例:&c) |
この表は、「scanf は型に応じて必要なポインタ型が変わる」ことを示しています。
例2:swap の引数型が合わないと交換サイズがズレる
swap が int * を受け取る関数だとします。
void swap_int(int *x, int *y);
ここに char の住所を渡すとどうなるか。
char は1バイトのつもりなのに、swap_int は int として扱い、sizeof(int) バイト単位で読み書きするつもりになります。
型違い swap が危険な理由
| 実際の交換したい対象 | 渡してしまった関数 | 関数が想定するサイズ | 起きうる問題 |
|---|---|---|---|
| char(1バイト) | swap_int(int *) | int のサイズ(例:4バイト) | 周辺メモリまで巻き込む可能性 |
じゃあどうする?(型に合わせた関数を作る)
安全にやるなら、型に合わせて関数を分けるのが基本です。
例:char 用の swap
void swap_char(char *x, char *y)
{
char temp = *x;
*x = *y;
*y = temp;
}
型ごとに作るメリット
| 方法 | メリット | デメリット |
|---|---|---|
| 型ごとに関数を作る | 安全で分かりやすい | 種類が増える |
| 1つで何でもやる(無理やり) | 関数は少ない | 型不一致で危険が増える |
入門段階では「安全で分かりやすい」を優先するのが正解です。
特殊なテクニックと注意(入門では避ける)
Cには、特殊な目的で「別の型として覗く」ようなテクニックも存在します。
ただしこれはルールや落とし穴が多く、入門の段階では混乱しやすいので、基本方針はこれでOKです。
Type * は Type を指すものとして使う(型を合わせる)
登場する命令(関数)と書式・何をする命令なのか
scanf
- 書式:scanf(書式文字列, 格納先1, 格納先2, ...);
- 何をする?:入力を読み取り、指定された格納先へ書き込む
- 重要:書式文字列の変換指定子と、格納先の型(アドレスの型)を一致させる
puts
- 書式:puts(文字列);
- 何をする?:文字列を表示し、改行も出す
printf
- 書式:printf(書式文字列, 引数1, 引数2, ...);
- 何をする?:書式に従って表示する
使った表や図の説明(まとめ)
- スカラ型の表は「ポインタも値として扱える量」という導入の整理です。
- ポインタ型の表は「Type * は Type を指す」という核心を整理しています。
- scanf 対応表は「変換指定子と格納先の型を一致させる」ためのチートシートです。
- バイトはみ出し図は「型不一致がメモリ破壊につながる」ことを直感で理解するための図です。
- swap の表は「関数側が想定するサイズが型で決まる」点を示しています。
