0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

この記事について

「C言語の基礎を学ぼう」をテーマに、自身の知識 + α をアドベントカレンダーにまとめます。
25日間でC言語をマスターしよう - Qiita Advent Calendar 2025 - Qiita

こんな方を対象としています

  • コンピュータがプログラムをどのように動かしているか知りたい/知らない方

  • プログラミングをしてみたい方

  • C言語初心者の方

キーワード

  • malloc

  • calloc

  • realloc

説明

malloc

変数宣言によるメモリ確保は、事前に大きさを決めておく必要がありました。
例えば、ユーザーが入力した文字列を配列に格納する場合、その文字列サイズ分ぴったりのメモリを確保することはできません。

プログラム内で必要な時に必要な分のメモリを確保する関数に malloc があります。

#include <stdio.h>
#include <stdlib.h> // malloc(), free()
#include <string.h> // strlen(), strcpy()

#define MAX_SIZE 256

int main(void) {
    char buf[MAX_SIZE];
    char *p;
    size_t length;

    printf("文字列を入力:");
    fgets(buf, MAX_SIZE, stdin);

    // 文字列の長さ
    length = strlen(buf);
    // charのサイズ * (文字列の長さ + ヌル文字分)を確保
    p = (int *)malloc(sizeof(char) * (length + 1));
    if (p == NULL) {
        printf("メモリ確保失敗");
        return 1;
    }
    // 確保した領域にデータをコピー
    strcpy(p, buf);

    printf("%s", p);

    // メモリ解放
    free(p);

    return 0;
}
文字列を入力:test
test

動的なメモリ確保をする上で注意する点は下記です。

  • sizeof を利用し必要なバイト数を計算して確保する
  • メモリ確保に失敗した場合の処理を定義する
  • 確保したメモリは必ず free() で解放する

calloc

callocmalloc と異なり、確保したメモリ領域を0で初期化してくれます。
両方の関数で確保した領域を比較してみましょう。

#include <stdio.h>
#include <stdlib.h> // calloc(), free()

int main(void) {
    int *p1, *p2;

    // malloc()で確保
    p1 = (int *)malloc(sizeof(int));
    if (p1 == NULL) {
        printf("メモリ確保失敗");
        return 1;
    }
    // calloc()で確保、intのサイズ1つ分
    p2 = (int *)calloc(1, sizeof(int));
    if (p2 == NULL) {
        printf("メモリ確保失敗");
        free(p1);
        return 1;
    }

    printf("p1:%d p2:%d", *p1, *p2);

    free(p1);
    free(p2);

    return 0;
}
p1:1022014208 p2:0

malloc() が確保した領域は不定の値になりますが、
calloc() が確保した領域は 0 に初期化されています。

realloc

realloc はすでに確保した領域のサイズを変更するときに使用します。

#include <stdio.h>
#include <stdlib.h> // realloc(), free()

int main(void) {
    int *p, *new_p;
    int i;

    // calloc()で確保、intのサイズ3つ分
    p = (int *)calloc(3, sizeof(int));
    if (p == NULL) {
        printf("メモリ確保失敗");
        return 1;
    }
    // realloc()で再確保、intのサイズ5つ分
    new_p = (int *)realloc(p, sizeof(int) * 5);
    if (new_p == NULL) {
        printf("メモリ確保失敗");
        free(p);
        return 1;
    }
    // reallocに成功した場合はpの領域は解放されている

    // 3つ分の要素はcallocにより初期化済み
    for (i = 0; i < 5; i++) {
        printf("new_p[%d]:%d\n", i, new_p[i]);
    }

    free(new_p);

    return 0;
}
new_p[0]:0
new_p[1]:0
new_p[2]:0
new_p[3]:5505089
new_p[4]:3997768

calloc() で確保した際に初期化された値を realloc() で確保した領域に引き継ぐことができていますね。

練習

1. CSVをJSONに変換しよう

任意のCSVファイルを読み込み、JSONファイルを作成しよう。

  • CSVの囲み文字は無いものとする
  • JSONの項目はすべて文字列項目とする
./resource/test.csv
code,name,price
1,apple,120
2,banana,200
3,coffee,80
./resource/test.json
[
    {
        "code":"1",
        "name":"apple",
        "price":"120"
    },
    {
        "code":"2",
        "name":"banana",
        "price":"200"
    },
    {
        "code":"3",
        "name":"coffee",
        "price":"80"
    }
]

ポイント

CSVファイルを1行ずつ読み込みます。
カンマで区切られた文字列を要素ごとに分割する関数を作成するとよいと思います。
例外処理時、ファイルクローズとメモリ解放をなるべく行うようにしましょう。
CSVファイルの1行目はヘッダーとして扱うこと、次の行が存在するときのみjsonの項目にカンマを付けること、に注意しましょう。

解答例

想定していたより大規模なプログラムになってしまいました...。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CSV_PATH_STR "./resource/test.csv"
#define JSON_PATH_STR "./resource/test.json"
#define MAX_BUF 256
#define MAX_ELEMENTS 20

int csv_to_array(char *csv_line, char **el_arr, int *el_num);

int main(void) {
    FILE *csv_fp, *json_fp; // 各ファイルポインタ
    char buf[MAX_BUF]; // ファイル1行分のデータ一時格納用
    char *header[MAX_ELEMENTS] = { NULL }; // CSVヘッダー管理ポインタ配列
    char *data[MAX_ELEMENTS] = { NULL }; // CSV1行分データ管理ポインタ配列
    int header_el_num = 0; // CSVヘッダーの要素数
    int data_el_num = 0; // CSV1行分データの要素数
    int first_data_flag = 1; // CSV1行目か判定するフラグ
    int first_el_flag = 1; // CSV1行の中の1つ目の要素か判定するフラグ
    int i = 0; // for文用

    // ファイル準備
    if ((csv_fp = fopen(CSV_PATH_STR, "r")) == NULL) {
        printf("csv open 失敗");
        return 1;
    }
    if ((json_fp = fopen(JSON_PATH_STR, "w")) == NULL) {
        printf("json open 失敗");
        fclose(csv_fp);
        return 1;
    }

    // CSVファイルのヘッダーを読み込む
    if (fgets(buf, MAX_BUF, csv_fp) == NULL) {
        printf("csv read 失敗");
        fclose(csv_fp);
        fclose(json_fp);
        return 1;
    }
    if (csv_to_array(buf, header, &header_el_num) != 0) {
        printf("csv to array (header) 失敗");
        fclose(csv_fp);
        fclose(json_fp);
        return 1;
    }

    // JSONファイルの書き込み開始
    fprintf(json_fp, "[\n");

    // CSVファイルを1行ずつ読み込み、JSONに変換する
    while (fgets(buf, MAX_BUF, csv_fp) != NULL) {
        // 1行分のデータを取得
        if (csv_to_array(buf, data, &data_el_num) != 0) {
            printf("csv to array (data) 失敗");
            break; 
        }

        // 最初のデータ行以外はカンマを入れて改行する
        if (!first_data_flag) {
            fprintf(json_fp, ",\n");
        }
        first_data_flag = 0;

        // CSV1行分のJSON変換
        first_el_flag = 1;
        fprintf(json_fp, "    {\n");
        for (i = 0; i < header_el_num && i < data_el_num; i++) {
            // 最初の要素以外はカンマを入れて改行する
            if (!first_el_flag) {
                fprintf(json_fp, ",\n");
            }
            first_el_flag = 0;

            // "key":"value" の形式で出力
            fprintf(json_fp, "        \"%s\":\"%s\"", header[i], data[i]);
        }
        fprintf(json_fp, "\n    }");

        // メモリ解放
        for (i = 0; i < data_el_num; i++) {
            free(data[i]);
            data[i] = NULL; // 間違えてアクセスしないよう初期化
        }
        data_el_num = 0; // 要素数をリセット
    }
    fprintf(json_fp, "\n]\n");

    // 後処理
    fclose(csv_fp);
    fclose(json_fp);
    for (i = 0; i < header_el_num; i++) {
        free(header[i]);
    }

    printf("成功");

    return 0;
}

/*
 * カンマで区切られた文字列を切り出す。
 * ・csv_line : カンマで区切られた文字列
 * ・el_arr : 切り出した要素の格納先
 * ・el_num : 切り出した要素の数
 */
int csv_to_array(char *csv_line, char **el_arr, int *el_num) {
    int el_cnt = 0; // 要素数
    char *check_p = csv_line; // 1文字ずつチェックするポインタ
    char *el_start = csv_line; // 要素先頭を指すポインタ
    while (1) {
        if (*check_p == ',' || *check_p == '\n' || *check_p == '\0') {

            // メモリ確保して文字列をコピーし、そのアドレスを記憶
            int el_length = check_p - el_start;
            char *el = (char *)malloc(sizeof(char) * (el_length + 1));
            if (el == NULL) {
                int i;
                for (i = 0; i < el_cnt; i++) {
                    free(el_arr[i]); // メモリ解放
                }
                return 1;
            }
            strncpy(el, el_start, el_length);
            el[el_length] = '\0';
            el_arr[el_cnt] = el;

            // 要素数を増やす
            el_cnt++;

            // 改行またはヌル文字は終端とみなす
            if (*check_p == '\n' || *check_p == '\0') {
                *el_num = el_cnt;
                return 0;
            }

            // 次の要素の開始位置を現在の位置の直後に設定
            el_start = check_p + 1;
        }

        // 次の文字へ移動
        check_p++;
    }
}
./resource/test.csv
code,name,price
1,apple,120
2,banana,200
3,coffee,80
./resource/test.json
[
    {
        "code":"1",
        "name":"apple",
        "price":"120"
    },
    {
        "code":"2",
        "name":"banana",
        "price":"200"
    },
    {
        "code":"3",
        "name":"coffee",
        "price":"80"
    }
]

メモリ確保の説明より、こちらのプログラムが記事のメインのようになってしまいました…。
ファイルパスをコマンドライン引数で受け取るようにすれば便利なツールになりそうですね。

おわりに

動的なメモリ確保をすることで、柔軟なプログラムを書くことができるようになります。
「まろっく、きゃろっく、りあろっく~♪」と覚えましょう。

参考文献

↓↓↓ はじめてプログラミングを学んだときに読んだ本です ↓↓↓
詳細(プログラミング入門 C言語)|プログラミング|情報|実教出版

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?