はじめに
【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
のように 空白を含む文字列もしっかり読めます。