
はじめてのC言語 | 第23回
はじめに
実践編2では、第20回の総合演習を main.c、student.c、student.h に分割しました。
今回は、そのプログラムをさらに改良して、学生の人数を実行時に入力できるようにします。
これまでの総合演習では、学生数を3人に固定していました。
動的メモリ確保を使うと、実行時に入力された人数に合わせて、必要な分だけ構造体配列を確保できます。
この回の目的は次の5点です。
- 固定長配列と動的配列の違いを理解する
mallocで構造体配列を確保する- 入力された人数に応じて処理する
freeで確保したメモリを解放する- 複数ファイル構成のまま動的メモリ確保を組み込む
今回の改良内容
実践編2では、main.c で次のように固定長の構造体配列を使っていました。
#define STUDENT_COUNT 3
struct Student students[STUDENT_COUNT];この書き方では、学生数は3人で固定です。
今回は、次のように人数を入力して、その人数分だけメモリを確保します。
students = malloc(sizeof(struct Student) * count);これにより、3人、5人、10人のように、実行時に人数を変えられるようになります。
今回のファイル構成
今回も、実践編2と同じく次の3つのファイルに分けます。
main.cstudent.cstudent.h
student.c と student.h の役割は大きく変わりません。
主な変更点は、main.c で固定長配列ではなく malloc を使うことです。
ヘッダファイル
student.h
#ifndef STUDENT_H
#define STUDENT_H
struct Student {
char name[20];
int score;
};
int input_students(struct Student students[], int size);
void print_students(struct Student students[], int size);
int sum_scores(struct Student students[], int size);
int save_result(struct Student students[], int size, int sum, double average);
#endifstudent.h は実践編2と同じです。
構造体定義と関数宣言を書いています。
学生情報を扱う関数
student.c
#include <stdio.h>
#include "student.h"
int input_students(struct Student students[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("name: ");
if (scanf("%19s", students[i].name) != 1) {
return 0;
}
printf("score: ");
if (scanf("%d", &students[i].score) != 1) {
return 0;
}
if (students[i].score < 0 || students[i].score > 100) {
return 0;
}
}
return 1;
}
void print_students(struct Student students[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%s %d\n", students[i].name, students[i].score);
}
}
int sum_scores(struct Student students[], int size) {
int i;
int sum = 0;
for (i = 0; i < size; i++) {
sum += students[i].score;
}
return sum;
}
int save_result(struct Student students[], int size, int sum, double average) {
FILE *fp;
int i;
fp = fopen("result.txt", "w");
if (fp == NULL) {
return 0;
}
for (i = 0; i < size; i++) {
fprintf(fp, "%s %d\n", students[i].name, students[i].score);
}
fprintf(fp, "sum %d\n", sum);
fprintf(fp, "average %.1f\n", average);
fclose(fp);
return 1;
}このファイルも実践編2と同じです。
配列が固定長配列でも、malloc で確保した領域でも、関数側では students[i] の形で扱えます。
人数を入力してメモリを確保する
main.c
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main(void) {
struct Student *students;
int count;
int sum;
double average;
printf("count: ");
if (scanf("%d", &count) != 1) {
printf("input error\n");
return 1;
}
if (count <= 0) {
printf("count must be positive\n");
return 1;
}
students = malloc(sizeof(struct Student) * count);
if (students == NULL) {
printf("memory allocation failed\n");
return 1;
}
if (input_students(students, count) == 0) {
printf("input error\n");
free(students);
return 1;
}
print_students(students, count);
sum = sum_scores(students, count);
average = (double)sum / count;
printf("sum: %d\n", sum);
printf("average: %.1f\n", average);
if (save_result(students, count, sum, average) == 0) {
printf("file open error\n");
free(students);
return 1;
}
free(students);
return 0;
}実行手順
1. 作業ディレクトリを作って移動する
mkdir final_dynamic_sample
cd final_dynamic_sample2. 3つのファイルを作成する
同じディレクトリに、次の3つのファイルを保存します。
main.cstudent.cstudent.h
3. コンパイルする
clang main.c student.c -o final_dynamic_app4. 実行する
./final_dynamic_app実行例:
count: 4
name: Alice
score: 80
name: Bob
score: 90
name: Carol
score: 70
name: Dave
score: 100
Alice 80
Bob 90
Carol 70
Dave 100
sum: 340
average: 85.05. 保存されたファイルを確認する
cat result.txt実行結果:
Alice 80
Bob 90
Carol 70
Dave 100
sum 340
average 85.0コードの読み方
struct Student *students
struct Student *students;students は、struct Student 型のデータを指すポインタです。malloc で確保した構造体配列の先頭アドレスを保存します。
malloc(sizeof(struct Student) * count)
students = malloc(sizeof(struct Student) * count);struct Student を count 個分保存できる領域を確保しています。
たとえば count が4なら、学生4人分の構造体配列を確保します。
students[i]
students[i].scoremalloc で確保した領域も、配列のように students[i] と書いて扱えます。
そのため、実践編2で作った関数を大きく変更せずに使えます。
エラー時の free
if (input_students(students, count) == 0) {
printf("input error\n");
free(students);
return 1;
}malloc に成功したあとでエラーが起きた場合も、終了前に free します。
確保したメモリを使い終わる前に処理を中止する場合でも、解放を忘れないようにします。
なぜ student.c を変更しなくてよいのか
input_students の引数は次の形です。
int input_students(struct Student students[], int size)関数の引数では、配列は先頭要素を指す形で渡されます。
固定長配列でも、malloc で確保した領域でも、先頭要素を指す情報として渡せます。
そのため、関数側では同じように students[i] でアクセスできます。
入力人数の確認
人数が0以下の場合は、配列として処理できません。
そのため、malloc の前に確認します。
if (count <= 0) {
printf("count must be positive\n");
return 1;
}この確認を入れることで、count が0や負の数のまま処理されることを防げます。
初心者がつまずきやすい点
#include <stdlib.h> を忘れる
malloc と free を使うには、#include <stdlib.h> が必要です。
#include <stdlib.h>malloc の戻り値を確認しない
malloc は失敗することがあります。
戻り値が NULL の場合は、確保に失敗しています。
if (students == NULL) {
printf("memory allocation failed\n");
return 1;
}エラー終了時に free を忘れる
malloc に成功したあとで処理を中止する場合も、free が必要です。
free(students);
return 1;sizeof(struct Student *) と書いてしまう
次の書き方は、ポインタのサイズを使ってしまいます。
malloc(sizeof(struct Student *) * count);ここで必要なのは、構造体そのもののサイズです。
正しくは次のように書きます。
malloc(sizeof(struct Student) * count);よくあるエラー
implicit declaration of function ‘malloc’
原因: #include <stdlib.h> を書いていません。
対処: main.c に #include <stdlib.h> を追加します。
memory allocation failed と表示される
原因: メモリ確保に失敗しています。
対処: 入力した人数が大きすぎないか確認します。
実行結果がおかしい
原因: count と関数に渡す要素数が一致していない可能性があります。
対処: input_students(students, count) のように、入力された人数をそのまま渡します。
異常終了する
原因: free したあとに students を使っている、または範囲外アクセスをしている可能性があります。
対処: free(students); は最後に行い、ループ条件は i < count にします。
練習用コード
最大点を求める関数を追加する
student.h に宣言を追加します。
int max_score(struct Student students[], int size);student.c に定義を追加します。
int max_score(struct Student students[], int size) {
int i;
int max = students[0].score;
for (i = 1; i < size; i++) {
if (students[i].score > max) {
max = students[i].score;
}
}
return max;
}main.c で呼び出します。
printf("max: %d\n", max_score(students, count));保存結果に人数を追加する
save_result の中で、人数も保存してみます。
fprintf(fp, "count %d\n", size);ファイルの先頭に書きたい場合は、学生一覧を出力する前に fprintf を追加します。
まとめ
今回のポイントは次のとおりです。
mallocを使うと、実行時に決まった人数分の領域を確保できる- 構造体配列も
mallocで確保できる sizeof(struct Student) * countで人数分のサイズを計算するmallocの戻り値はNULLチェックするmallocに成功したら、最後にfreeする- エラーで途中終了する場合も、確保済みなら
freeする - 固定長配列でも動的に確保した配列でも、関数側では
students[i]の形で扱える
この回では、第20回の総合演習をさらに改良し、学生の人数を実行時に入力できるようにしました。malloc を使うことで、必要な人数分だけ構造体配列を確保できます。
次回予告
次は、このシリーズの締めくくりとして、「C言語入門シリーズまとめ」を学びます。
ここまで、配列やポインタ、構造体、ファイル入出力、そして動的メモリ確保まで進んできました。
特に、動的メモリ確保では「確保 → 確認 → 使用 → 解放」という流れを守ることがとても大切です。
この流れを意識できるようになると、プログラムはぐっと安定し、安心して扱えるようになります。
そして今、あなたはこれまで学んだ要素を組み合わせて、小さな実用プログラムを作れる段階に来ています。
次回は、それらの知識をもう一度つなぎ直し、「バラバラだった理解が1つになる瞬間」を一緒に確認していきましょう。


