実践編3|C言語で人数を動的メモリ確保で可変にする:入力された人数分だけ構造体配列を確保する

当サイトでは、コンテンツの一部に広告を掲載しています。
実践編3|C言語で人数を動的メモリ確保で可変にする:入力された人数分だけ構造体配列を確保する

はじめてのC言語 | 第23回

目次

はじめに

実践編2では、第20回の総合演習を main.cstudent.cstudent.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.c
  • student.c
  • student.h

student.cstudent.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);

#endif

student.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_sample

2. 3つのファイルを作成する

同じディレクトリに、次の3つのファイルを保存します。

  • main.c
  • student.c
  • student.h

3. コンパイルする

clang main.c student.c -o final_dynamic_app

4. 実行する

./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.0

5. 保存されたファイルを確認する

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 Studentcount 個分保存できる領域を確保しています。
たとえば count が4なら、学生4人分の構造体配列を確保します。

students[i]

students[i].score

malloc で確保した領域も、配列のように 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> を忘れる

mallocfree を使うには、#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つになる瞬間」を一緒に確認していきましょう。

あわせて読みたい
実務編4 | C言語入門シリーズまとめ:基本文法から小さな実用プログラムまで はじめてのC言語 | 第24回 はじめに このシリーズでは、C言語の基本文法から始めて、配列、文字列、ポインタ、構造体、ファイル入出力、動的メモリ確保までを順番に学...

さらに学びたいあなたへ

用途ごとに選ぶ Linux のおすすめ本

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

Created by UNIX Cafe

目次