C++のsize_tとptrdiff_tを徹底解説!標準型の役割と使い分け
生徒
「先生、C++のコードを読んでいると int 以外に size_t という言葉が出てくるのですが、これって何ですか?」
先生
「それは『標準型』と呼ばれる特別な型の一つですよ。パソコンの種類によってメモリの扱い方が違うのを、C++が上手く調整してくれるための道具なんです。」
生徒
「普通の int じゃダメなんですか?」
先生
「良いところに気づきましたね。実は int だけだと、ものすごく大きなデータを扱う時に不都合が起きることがあるんです。安全にプログラミングをするために欠かせない size_t や ptrdiff_t の役割を、一緒に学んでいきましょう!」
1. size_tとは?「大きさ」を測るための専用のもの
C++において、size_t(サイズ・ティー)は、オブジェクトやデータの「大きさ」や「個数」を表すために作られた特別なデータ型です。最大の特徴は、符号なし整数であることです。つまり、0以上の正の数しか扱いません。
プログラミング未経験の方に分かりやすく例えると、これは「定規の目盛り」のようなものです。物の長さを測るときに「マイナス5センチ」とは言いませんよね?それと同じで、メモリ上のサイズや配列の要素数(データの数)を数えるときには、マイナスになることがないので、この size_t が使われるのです。C++の変数の中でも、データの管理に特化した非常に重要な役割を持っています。
2. なぜ int ではなく size_t を使うの?
「数字なら全部 int でいいじゃないか」と思うかもしれません。しかし、そこには大きな罠があります。実は int が表せる数字の範囲は、パソコンの仕組み(OSやCPU)によって決まっていますが、最近の大きなメモリを積んだパソコンでは、int の範囲を超えてしまうデータ量を扱うことがあります。
size_t は、そのパソコンが扱える「最大のメモリサイズ」に合わせて自動的に大きさが変わるように設計されています。32ビットのパソコンなら32ビット分、64ビットのパソコンなら64ビット分の範囲をカバーします。これにより、どんなに巨大な配列やファイルでも、エラーを起こさずに数え上げることができるのです。これがプログラミングにおける「移植性(いしょくせい:別の環境でも動くこと)」を支える知恵です。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
// 配列の要素数を受け取る時は size_t を使うのが標準的
size_t count = numbers.size();
std::cout << "データの数は " << count << " 個です。" << std::endl;
return 0;
}
3. ptrdiff_tとは?「距離」を表すためのもの
次に、ptrdiff_t(ポインターディフ・ティー)について解説します。これは、二つの地点(ポインタ)の「差」を表すための型です。size_t と違って、こちらは符号あり整数です。つまり、プラスもマイナスも扱えます。
例えるなら、これは「地点Aから地点Bまでの歩数」です。Aから見てBが先にあればプラス、手前にあればマイナスになりますよね。メモリという広大な土地の中で、「あるデータから見て、別のデータがどれくらい離れているか」を計算するときに使われます。演算子を使ってメモリの位置を引き算したときの結果を受け取るための、専用の型なのです。
4. 標準型のメリット:どんなパソコンでも正しく動く
これらの型は stddef.h や cstddef という場所に定義されています。これらを使う最大のメリットは、環境依存(かんきょういぞん)のバグを防げることです。
昔のパソコンで作ったプログラムを、最新の高性能なパソコンで動かそうとしたとき、もし「数え上げ」を適当な型でやっていると、数字が溢れ出してしまう(オーバーフロー)ことがあります。size_t や ptrdiff_t を使っていれば、C++がそのパソコンに合わせて「最適な定規」を用意してくれるので、私たちは安心してコードを書くことに集中できるのです。
5. 基本的な使い分けのルール
初心者の方が迷わないように、シンプルな使い分けの基準を整理しました。これさえ覚えておけば、基本構文の作成で困ることはありません。
| 型名 | 種類 | いつ使う? |
|---|---|---|
| size_t | 正の数のみ | データの個数、配列の添字(番号)、メモリのサイズ |
| ptrdiff_t | 正も負もOK | データ同士の距離、位置の引き算結果 |
| int | 正も負もOK | 一般的な計算、回数が決まっているループ |
6. 実践!ループ処理で使ってみよう
実際に size_t を使って、リストの中身を全部表示するプログラムを書いてみましょう。配列の番号(インデックス)は 0 から始まるので、マイナスになる必要がありません。そのため size_t が最適です。
#include <iostream>
int main() {
const char* fruits[] = {"リンゴ", "バナナ", "オレンジ"};
// sizeof(fruits) / sizeof(fruits[0]) は配列の個数を求める計算です
// この結果は size_t 型になります
for (size_t i = 0; i < 3; i++) {
std::cout << i << "番目の果物: " << fruits[i] << std::endl;
}
return 0;
}
実行結果は以下のようになります。
0番目の果物: リンゴ
1番目の果物: バナナ
2番目の果物: オレンジ
7. 符号なし型の注意点:マイナスに注意!
size_t を使う時に一つだけ注意してほしいのが、マイナスの計算です。size_t は正の数しか持てないので、もし 0 から 1 を引いてしまうと、エラーにならずに「ものすごく大きなプラスの数」に化けてしまいます。これをアンダーフローと呼びます。
逆方向に数えるようなループ(3, 2, 1, 0...)を作るときに size_t を使うと、0 の次はマイナスにならずに巨大な数字になってループが止まらなくなることがあります。引き算をする可能性があるときは、ptrdiff_t や int を検討するようにしましょう。これが制御構文を安全に扱うコツです。
8. メモリのアドレスとポインタの関係
C++では、データが保存されている場所を「住所(アドレス)」として管理します。この住所を指し示すのがポインタです。そして、その住所の値を扱うときや、住所同士の間隔を測るときに、今回学んだ size_t や ptrdiff_t が裏側で活躍しています。
パソコンのメモリという巨大な倉庫の中で、どこに何があるかを正確に把握するためには、倉庫の広さに対応した「大きな数字を扱える型」が必要です。これらの型を使いこなすことは、パソコンの仕組みを深く理解することに繋がります。
#include <iostream>
#include <cstddef> // ptrdiff_t を使うために必要
int main() {
int data[10] = {0};
int* first = &data[0]; // 最初の場所
int* last = &data[5]; // 6番目の場所
// 場所同士の引き算
ptrdiff_t distance = last - first;
std::cout << "地点の差は " << distance << " です。" << std::endl;
return 0;
}
9. 標準型を使いこなしてプロの仲間入り
最初は int だけで十分かもしれませんが、開発が進むにつれて「なぜかプログラムが動かない」「環境を変えたら壊れた」という問題に突き当たることがあります。そんな時、size_t のような標準型を正しく選べていれば、それだけで多くのトラブルを未然に回避できます。
C++には歴史があり、多くのパソコンで動くように工夫が凝らされています。今回学んだ型は、その歴史の中で磨かれた「安全装置」のようなものです。ぜひ、日々のプログラム作成の中で積極的に使ってみてください。一つ一つの型に意味があることを知ることで、あなたの書くコードはより洗練されたものになっていきますよ!