
C言語入門|関数活用の落とし穴
ここまで、本当にお疲れさまでした。
第8章では、関数についてじっくり学んできましたね。
- 引数と戻り値の役割
- 値の受け渡しの正体
- 構造体や配列を扱う関数
「もう関数は使いこなせている気がする!」
そんな手応えを感じている人も多いと思います。
ですが―
ちょうど今が、最も危険なタイミングでもあるのです。

String型っぽいものを定義してみると…
これまで自然に使ってきた「文字列」について、改めて考えてみましょう。
typedef char String[1024];一見すると、
「String型=便利な文字列型」
のように見えます。
しかし、これはあくまで char型の配列 です。
ここを誤解した瞬間から、落とし穴が始まります。
String型に課せられた厳しいルール
この String型っぽいものには、次のようなルールがあります。
| ルール | 内容 |
|---|---|
| ① | 配列サイズ1024を小さくしてはいけない。 |
| ② | 1024文字すべて使えると思ってはいけない。 |
| ③ | 初期化以外で = 代入してはいけない。 |
| ④ | 計算や連結に使ってはいけない。 |
| ⑤ | == で比較してはいけない。 |
| ⑥ | 関数の引数や戻り値に使ってはいけない。 |
| ⑦ | この章のラストで理由を整理します。 |
「えっ!?
配列や文字列を引数で渡す話、さっきまでしてましたよね?」
はい、そのとおりです。
今までは学習を進めるために、あえて踏み込んでいただけなのです。
なぜ、そこまで慎重になる必要があるのか
正直に言います。
C言語において、
関数の引数や戻り値として
文字列や配列を扱うことは、
非常に高いリスクを伴います
これは初心者だけの話ではありません。
- メモリの破壊
- 想定外のデータ書き換え
- 原因不明の暴走
- システム全体への影響
こうした事故は、
熟練エンジニアであっても実際に起こしています。
これまでのサンプルは「安全な例」だけだった
これまで紹介してきたコードは、
- サイズが合っている。
- 寿命の問題が出ない。
- 偶然トラブルが起きない。
という条件を満たしたものだけでした。
つまり私たちは、
事故が起きないルートだけを歩いていた
という状態だったのです。
関数と配列が組み合わさると何が起きる?
次のような関数を見てみましょう。
// 指定点以上のスコアを持つ人数を数える関数
// 引数:点数配列、基準点
// 戻り値:人数
int countQualified(int scores[5], int border)
{
int count = 0;
for (int i = 0; i < 5; i++) {
if (scores[i] >= border) {
count++;
}
}
return count;
}呼び出し側です。
int scores[5] = {72, 88, 91, 60, 79};
int result = countQualified(scores, 80);
printf("条件を満たした人数は %d 人です\n", result);実行結果
条件を満たした人数は 2 人です正常に動いています。
しかし、ここに大きな前提があります。
配列は「値渡し」ではない
関数に渡されているのは、
- 配列の中身
ではなく - 配列の先頭アドレス
です。
つまり関数は、
呼び出し元の配列を
直接操作できる立場
にあります。
うっかり書き換えれば、
呼び出し元のデータは壊れます。
これが、関数活用に潜む最大の落とし穴です。
構造体なら安全なのか?
構造体の場合は、少し事情が異なります。
// ステータスを更新する関数
// 引数:プレイヤー構造体
// 戻り値:更新後のプレイヤー
Player updateStatus(Player p)
{
p.level += 1;
return p;
}
Player hero = {"Knight", 5};
hero = updateStatus(hero);
printf("レベルは %d になりました\n", hero.level);表示結果
レベルは 6 になりました構造体は基本的に 値としてコピー されます。
そのため、配列よりは安全です。
ただし、
構造体の中に配列やポインタを含めた瞬間、
再び危険領域に入ります。
「使える」ことと「使っていい」は違う
ここまでの話をまとめると、こうなります。
- 書けるから安全とは限らない。
- 動くから正しいとは限らない。
- 便利だから使うのは危険
だからこそ、実務や本格開発では、
- グローバル変数を避ける。
- 配列や文字列を直接渡さない。
- 管理できる形に変換する。
といったルールが生まれました。
複合リテラル:その場で集成体型の実体を生み出す
とはいえ、
「一度作って、すぐ関数に渡すのは面倒…」
という場面もあります。
Student s = {"Bob", 76};
printStudent(s);そんなときに使えるのが 複合リテラル です。
近年の C 言語では、複合リテラル(compound literal)という構文を使って、関数呼び出しの式の中で集成体型の実体を生み出し、即時利用できるようになっています。
書式
(型名){初期化指定}構造体の例
printStudent( (Student){"Bob", 76} );配列の例
int total = sumValues( (int[4]){10, 20, 30, 40} );その場で生成し、
使い終わったら消える。
この書き方は、
安全に使える場面がはっきりしています。
新しい「最後のルール」
ルール⑦
C言語は「仕組みを理解して初めて安全に使える言語」である。
仕組みを知らないまま便利さだけを使ってはいけない。
このルールを意識できたとき、
C言語は「怖い言語」から
「信頼できる道具」に変わります。
