はじめに
以下の前回の記事では、変数や配列のアドレスを printf("%p")
で出力しながら、ポインタの基本を体験しました。
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 導入編)
今回はその発展編として、構造体をレコード形式で管理し、realloc
を使って可変長リストを扱う方法を紹介します。
これは実務でもよく出てくるテーマで、データベースのように「レコードを順次追加して管理する」といった場面に直結します。
サンプルコード
今回のサンプルコードです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 学生レコードの構造体
typedef struct {
int id;
char name[50];
int age;
} Student;
int main(void) {
int count = 0;
Student *students = NULL; // 動的配列(最初はNULL)
// 1人追加
count++;
students = realloc(students, count * sizeof(Student));
students[0].id = 1;
strcpy(students[0].name, "Taro");
students[0].age = 18;
// さらに追加
count++;
students = realloc(students, count * sizeof(Student));
students[1].id = 2;
strcpy(students[1].name, "Hanako");
students[1].age = 19;
// 出力
for (int i = 0; i < count; i++) {
printf("%d: %s (%d)\n", students[i].id, students[i].name, students[i].age);
}
free(students);
return 0;
}
実行結果(例)
1: Taro (18)
2: Hanako (19)
今回のサンプルコードのポイント解説
- 構造体で1レコードを定義
→ Student 型を定義して、id・name・age をまとめて管理できます。 - 動的配列(Student)を使う
→ 最初は NULL で用意しておき、realloc でサイズを増やします。 - realloc の特徴
- 元データを保持したまま、領域を拡張できます。
- ただし内部的に別の場所へコピーされることもあるので、必ず students = realloc(...) のように「戻り値を再代入」する必要があります。
- 最後は free で解放
→ 動的に確保したメモリは free で解放しないとメモリリークにつながります。
ここまでのまとめ
malloc + realloc を組み合わせることで 可変長のリスト を実現できる
- 構造体と組み合わせると、実務的なレコード管理に直結する
- 今後はこれを応用して「リンクリスト」「木構造」など、より複雑なデータ構造へ発展させることができる
reallocに関する補足
ここでの realloc は便利ですが、大規模なデータを何度も拡張する場合にはオーバーヘッドが発生します。実務では「ある程度まとめて確保」して効率化する設計もよく使われます。
まずは仕組みを理解するために、小さな例で試すのがオススメです。
ポインタ演算版でのサンプルコード
次はさらに一歩進めて、ポインタ演算だけで同じ処理を書く方法を紹介します。
C言語では配列とポインタが密接に結びついており、arr[i]
は *(arr + i)
の配列構文です。ポインタ変数だけを使うことで、配列の仕組みがより実感できるはずです。
今回のサンプルコード(ポインタ演算版)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 学生レコードの構造体
typedef struct {
int id;
char name[50];
int age;
} Student;
int main(void) {
int count = 0;
Student *students = NULL; // 動的配列
Student *p; // 作業用ポインタ
// 1人目を追加
count++;
students = realloc(students, count * sizeof(Student));
p = students + (count - 1); // 最後の要素を指す
p->id = 1;
strcpy(p->name, "Taro");
p->age = 18;
// 2人目を追加
count++;
students = realloc(students, count * sizeof(Student));
p = students + (count - 1); // 最後の要素を指す
p->id = 2;
strcpy(p->name, "Hanako");
p->age = 19;
// 出力(ポインタを動かしながら)
for (p = students; p < students + count; p++) {
printf("%d: %s (%d)\n", p->id, p->name, p->age);
}
free(students);
return 0;
}
実行結果(例)
前項のプログラムと同じです。
1: Taro (18)
2: Hanako (19)
ポインタ演算版のポイント解説
- students + (count - 1)
→ 配列の最後の要素をポインタで表している
(例: students + 1 は &students[1] と同じ意味) - p->id
→ (*p).id の省略形。ポインタで構造体メンバにアクセスする標準的な書き方。 - ループの書き方
for (p = students; p < students + count; p++)
とすれば、インデックスを使わずにポインタを進めながら要素を処理できる。
ここでのまとめ
配列インデックスを使わずに ポインタ演算+->演算子 でレコードを操作できる
arr[i] と *(arr + i) が等価であることを実感できる
初学者には配列インデックスの方が分かりやすいが、ポインタ版を体験することで「C言語らしい書き方」や配列とポインタの関係が理解しやすくなる
ここでの補足
今回のコードはシンプルさを優先して realloc を直接使いましたが、実務では エラーチェック(realloc が失敗した場合の処理)を入れることが推奨されます。
学習の第一歩としては「ポインタで配列を歩く」ことを体験するのが目的です。
配列とポインタの関係について
配列とポインタはよく「同じものだ」と説明されることがあります。
しかし、正しくは 「密接に関係しているが別物」 です。
配列
- 宣言時にサイズが決まり、変更できない
- 実体は「決まった長さの連続したメモリ領域」
- 配列名は式の中で 先頭要素のアドレスに暗黙変換 される
ポインタ
- 「アドレスを保持する変数」
- 代入によって、どのメモリを指すかを自由に変えられる
-
malloc
/free
と組み合わせると動的にメモリを扱える
似ている理由
-
arr[i]
は*(arr + i)
に展開される -
p[i]
も*(p + i)
に展開される - そのため「同じように使える」と説明されがち
配列とポインタのまとめ
- 配列は領域そのもの
- ポインタはアドレスを保持する変数
- 使い方が似ているため混同されやすいが、概念としては異なる
全体的なまとめ:配列から始まるデータ構造の考え方
今回の内容では、配列を使ってアドレス操作・ポインタ操作の基本を体験しました。
ここから先は、より複雑なデータ構造の理解につながっていきます。
-
🔹 配列はデータ構造の入り口
配列では「要素が連続したメモリ領域に並ぶ」という基本を学びました。
これは、すべてのデータ構造の出発点です。 -
🔹 アドレスを管理すれば自由に構造を作れる
各データをアドレスでつなげれば、配列以外の構造も作れます。
たとえば:- リスト(順番に次を指す)
- ツリー(親・子の関係をポインタで表す)
- スタック/キュー(追加・取り出しを制御)
-
🔹 高水準言語ではライブラリが支えてくれる
C++、C#、Java、Pythonなどの言語には、これらのデータ構造を扱うための標準ライブラリが用意されています。
開発者はそれを使うだけで、内部のポインタ処理を意識せずに済みます。 -
🔹 でもポインタを知っていると強い
メモリ上で何が起きているかを理解できると、
・効率的なデータ処理
・バグの特定や最適化
・低レイヤとの連携(組込み、画像処理など)
などで強みを発揮できます。
さいごに
今回の配列とポインタの操作は、データ構造を理解する第一歩です。配列を通して「アドレスをどう扱うか」が分かると、その先の世界(リスト、ツリー、メモリ管理)が一気に開けます。そこが見えてくる一歩になればと思います。
ありがとうございます。
(追記)今回で一段落させようと思ったのですが、もう一段階進めたほうがいいかなと思ったので、延長編として以下を投稿しました。
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 発展編)