はじめに
この記事について
「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
calloc は malloc と異なり、確保したメモリ領域を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の項目はすべて文字列項目とする
code,name,price
1,apple,120
2,banana,200
3,coffee,80
[
{
"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++;
}
}
code,name,price
1,apple,120
2,banana,200
3,coffee,80
[
{
"code":"1",
"name":"apple",
"price":"120"
},
{
"code":"2",
"name":"banana",
"price":"200"
},
{
"code":"3",
"name":"coffee",
"price":"80"
}
]
メモリ確保の説明より、こちらのプログラムが記事のメインのようになってしまいました…。
ファイルパスをコマンドライン引数で受け取るようにすれば便利なツールになりそうですね。
おわりに
動的なメモリ確保をすることで、柔軟なプログラムを書くことができるようになります。
「まろっく、きゃろっく、りあろっく~♪」と覚えましょう。
参考文献
↓↓↓ はじめてプログラミングを学んだときに読んだ本です ↓↓↓
詳細(プログラミング入門 C言語)|プログラミング|情報|実教出版