1
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言語【Day 3: 関数・構造体・メモリ確保】

Last updated at Posted at 2025-08-27

はじめに

【C言語】 は、1970年代に開発された汎用プログラミング言語で、現在の多くの言語(C++、Java、C#、Go など)の基礎となっています。

特徴としては、OS、デバイスドライバ、組み込みシステムなど、「ハードウェアに近い低レベルな処理」が可能な点です。

【注意】このメモは、C言語を全くしらない人間が、ChatGPTに聞いて適当に書いている、C言語の基本的な記載方法のまとめです。

(内容は確認しながら書いているつもりですが、間違ってたらごめんなさい)


関数とは?

処理をまとめて再利用できる仕組み

同じ処理を繰り返し書かずに、部品化して使い回せます。

#include <stdio.h>

// 足し算を行う関数
int add(int a, int b) {
    return a + b;
}

int main(void) {
    int result = add(3, 5);
    printf("3 + 5 = %d\n", result);
    return 0;  //終了ステータス:エラー正常終了
}


📌 出力結果

3 + 5 = 8

関数の基本

・戻り値の型 関数名(引数) の形で定義
・return で値を返す
・引数は 値渡し(コピーされる)


ポインタを使った関数呼び出し

#include <stdio.h>

// 値を2倍にする関数
void doubleValue(int *p) {
    *p = *p * 2;
}

int main(void) {
    int x = 10;
    doubleValue(&x);   // &x を渡す
    printf("x = %d\n", x); // 20
    return 0;
}


📌 出力結果

x = 20

👉 ポインタを使うと「関数の外の変数」を直接操作できる。

※ C++の参照渡しに似ていますが、Cではポインタを通して間接的に値を変える必要があります。

ポインタ経由の間接アクセスが必要

Cでは、関数に「変数そのもの」を渡すことはできません。渡されるのは 変数のコピー か 変数のアドレス(ポインタ) です。

・変数のコピーを渡す → 関数内で変更しても呼び出し元には影響しない

void foo(int x) {
    x = 10; // 呼び出し元には影響しない
}

・変数のアドレス(ポインタ)を渡す → 関数内で間接的に値を書き換えられる(この「ポインタを通して書き換える」ことを 間接アクセス と言います)

呼び出し元の変数を変更したい場合は、
ポインタ経由で操作する必要があります。

void foo(int *p) {
    *p = 10; // ポインタ経由で呼び出し元を変更
}

構造体とは?

異なる型をひとまとめにできるデータ型

配列は同じ型しか持てませんが、構造体なら複数の型を 1 つにできます。

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

// 構造体の定義
struct Person {
    char name[20];
    int age;
};

int main(void) {
    struct Person p1;
    strcpy(p1.name, "Alice");
    p1.age = 20;

    printf("名前: %s, 年齢: %d\n", p1.name, p1.age);
    return 0;
}


📌 出力結果

名前: Alice, 年齢: 20

・struct とは
複数のデータをまとめて扱える型です。

・メンバへのアクセスは . 演算子

👉 配列は同じ型しか持てないが、構造体なら文字列+整数など異なる型をまとめられる。


構造体と関数

構造体を関数に渡すとコピーになるので非効率です。
ポインタを使えば「元の構造体」を直接操作できます。

void birthday_copy(struct Person p) {
    p.age += 1;  // コピーが変わるだけ
}


👉 この場合、元の Person の年齢は変わりません。


構造体とポインタ

構造体を効率的に扱うためには ポインタ(住所) を渡します。
こうすると関数は コピーではなく元のデータ を直接操作できます。

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

struct Person {
    char name[20];
    int age;
};

// 構造体へのポインタを受け取る
void birthday(struct Person *p) {
    p->age += 1;  // (*p).age と同じ → 元の構造体を変更
}

int main(void) {
    struct Person *p = malloc(sizeof(struct Person));
    strcpy(p->name, "Bob");
    p->age = 30;

    birthday(p); // ポインタを渡すので元の値が更新される

    printf("名前: %s, 年齢: %d\n", p->name, p->age);

    free(p); // メモリ解放
    return 0;
}



📌 出力結果

名前: Bob, 年齢: 31


💠実体(普通の変数)の場合
👉 → 実体からアクセス

struct Person {
    char name[20];
    int age;
};

int main(void) {
    struct Person p;    // 構造体の「実体」
    p.age = 20;         // 「.」でメンバにアクセス
}



💠ポインタ(住所を持っている変数)の場合

int main(void) {
    struct Person *p;         // 構造体の「ポインタ」
    p = malloc(sizeof(struct Person));
    p->age = 20;              // 「->」でメンバにアクセス
}

👉 -> は「ポインタ経由でアクセスする演算子」
これは (*p).age の省略形。

つまり「住所(ポインタ)が指している先の構造体の中の age」にアクセスする。


malloc/freeとは?

実行時に必要なメモリを確保する仕組み

配列はサイズ固定ですが、
malloc を使えば動的に確保できます。
使い終わったら 必ず free で必ず解放します。

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

int main(void) {
    int n = 5;
    int *arr = malloc(sizeof(int) * n); // int 5個分のメモリ確保

    for (int i = 0; i < n; i++) {
        arr[i] = (i+1) * 10;
    }

    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    free(arr); // 解放
    return 0;
}


📌 出力結果

arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50

👉 malloc/freeのポイント

malloc(バイト数) でピープ領域に動的にメモリを確保

free(ポインタ) で解放

解放しないと「メモリリーク」になる


💠応用:構造体の配列を動的に確保

例えば「人の名簿」を構造体配列として管理できます。

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

struct Person {
    char name[20];
    int age;
};

int main(void) {
    int n = 3;
    struct Person *list = malloc(sizeof(struct Person) * n);

    strcpy(list[0].name, "Alice"); list[0].age = 20;
    strcpy(list[1].name, "Bob");   list[1].age = 25;
    strcpy(list[2].name, "Carol"); list[2].age = 30;

    for (int i = 0; i < n; i++) {
        printf("%s (%d歳)\n", list[i].name, list[i].age);
    }

    free(list);
    return 0;
}


📌 出力結果

Alice (20歳)
Bob (25歳)
Carol (30歳)

👉 配列 × 構造体 × 動的確保 で柔軟なデータ管理が可能に。


⚠️ ポインタの注意点(バグの温床)

ポインタは便利ですが、誤用すると クラッシュやバグの原因 になります。
特に注意すべきは以下の 3 つとなります。


1️⃣ NULLポインタ

#include <stdio.h>
int main(void) {
    int *p = NULL;

    if (p == NULL) {
        printf("p は NULL です\n");
    }

    // *p = 10;   // ❌ エラー(アクセス違反)
    return 0;
}


👉 使用前に必ず NULLチェック をする。


2️⃣ ダングリングポインタ(解放済みを使う)

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

int main(void) {
    int *p = malloc(sizeof(int));
    *p = 123;
    printf("%d\n", *p);

    free(p);   // 解放

    // printf("%d\n", *p); // ❌ 未定義動作
    p = NULL;  // ✅ 安全対策
}

👉 free したら 必ず NULL を代入。


3️⃣ 初期化忘れ

#include <stdio.h>
int main(void) {
    int *p;        // 初期化していない
    // *p = 10;    // ❌ どこを壊すかわからない
    return 0;
}


👉 ポインタは 必ず初期化。

まだなら NULL
すぐ使うなら &変数 や malloc


📌 トピック

関数内でポインタを変更し、新しいメモリを割り当てる

関数の中で新しいメモリを確保してポインタを差し替える例です。
この場合、二重ポインタを使う必要があります。

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

// 新しい文字列を割り当ててポインタを差し替える関数
void changeString(char **p, const char *newStr) {
    // 新しいメモリを確保
    *p = (char *)malloc(strlen(newStr) + 1);

    // メモリ確保成功したらコピー
    if (*p != NULL) {
        strcpy(*p, newStr);
    }
}

int main(void) {
    char *msg = NULL;   // まだ何も指していない

    // 関数内で新しい文字列を割り当てる
    changeString(&msg, "Hello from function!");

    printf("%s\n", msg);  // Hello from function!

    // 使用後は解放
    free(msg);

    return 0;
}


📌 出力結果

Hello from function!

changeString(&msg, "Hello from function!");
→ &msg を渡しているので 二重ポインタ char ** で受け取れる。

関数の中で malloc によって新しいメモリを確保し、
*p に代入することで msg が新しい領域を指すように変更される。


char **messages に入れるパターン

→ 実行時にメッセージ数が決まるとき

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

int main(void) {
    int n;
    printf("文字列の数を入力してください: ");
    scanf("%d", &n);

    // char* の配列を動的に確保
    char **messages = malloc(n * sizeof(char *));
    if (messages == NULL) return 1;

    for (int i = 0; i < n; i++) {
        char buffer[100];
        printf("messages[%d] を入力: ", i);
        scanf("%99s", buffer);

        // 入力をコピーして格納
        messages[i] = malloc(strlen(buffer) + 1);
        strcpy(messages[i], buffer);
    }

    // 出力
    printf("\n=== 入力された文字列 ===\n");
    for (int i = 0; i < n; i++) {
        printf("messages[%d] = %s\n", i, messages[i]);
        free(messages[i]);  // 各文字列を解放
    }

    free(messages); // 配列自体を解放
    return 0;
}


👉 このまま実行すると

文字列の数を入力してください: 3
messages[0] を入力: Apple
messages[1] を入力: Banana
messages[2] を入力: Cherry

📌 出力結果

=== 入力された文字列 ===
messages[0] = Apple
messages[1] = Banana
messages[2] = Cherry

💡補足

if (messages == NULL) return 1;
→ malloc が失敗した場合のチェック。確保できなければ NULL が返る。

char buffer[100]; scanf("%99s", buffer);
→ 一時バッファに最大99文字まで読み込む(%99s で終端 \0 を含めて安全確保)。

char **messages は「文字列を指すポインタの配列」。
実行時に個数が決まる場合は malloc で配列を確保し、各要素ごとに文字列用メモリも確保してコピーする。

解放は 各文字列 → 配列自体 の順で free。忘れるとメモリリークになる。

実際には scanf("%s") より fgets などが安全(空白を含む文字列対応)。

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

int main(void) {
    int n;
    printf("文字列の数を入力してください: ");
    if (scanf("%d", &n) != 1) return 1;

    // 改行を捨てる(次の fgets が誤動作しないように)
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) {}

    // char* の配列を動的に確保
    char **messages = malloc(n * sizeof(char *));
    if (messages == NULL) return 1;

    for (int i = 0; i < n; i++) {
        char buffer[100];
        printf("messages[%d] を入力: ", i);

        if (fgets(buffer, sizeof buffer, stdin) == NULL) {
            perror("入力エラー");
            return 1;
        }
        buffer[strcspn(buffer, "\n")] = '\0'; // 改行を削除

        // 入力をコピーして格納
        messages[i] = malloc(strlen(buffer) + 1);
        if (messages[i] == NULL) return 1;
        strcpy(messages[i], buffer);
    }

    // 出力
    printf("\n=== 入力された文字列 ===\n");
    for (int i = 0; i < n; i++) {
        printf("messages[%d] = %s\n", i, messages[i]);
        free(messages[i]);  // 各文字列を解放
    }

    free(messages); // 配列自体を解放
    return 0;
}


✅ 特徴

fgets を使っているので 空白を含む文字列もOK。

入力時に残る改行は strcspn で削除。

scanf("%d", &n) の後の 改行除去 が重要(これをしないと1回目の fgets が空行を読む)。

👉 このまま実行すると

文字列の数を入力してください: 2
messages[0] を入力: Hello World
messages[1] を入力: C language

📌 出力結果

=== 入力された文字列 ===
messages[0] = Hello World
messages[1] = C language

のように 空白を含む文字列もしっかり読めます。







1
0
2

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
1
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?