トラック8・🗃️配列

配列を関数に渡す——入口編

読む目安 約13分・ゴール:配列を関数へ渡す書き方を身につけ、値渡しとの違いに気づける

アプリでこのトピックを学ぶ(無料・登録不要)

このトピックの要点

配列も関数に渡せます。書き方はこうです。

#include <stdio.h>

int sum_of(int a[], int n)      // 配列と、その要素数を受け取る
{
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += a[i];
    }
    return sum;
}

int main(void)
{
    int score[5] = {60, 85, 40, 90, 75};
    printf("%d\n", sum_of(score, 5));   // 350
    return 0;
}

渡すときは score と角カッコなしの名前だけを書きます。受け取る側は int a[] と書き、個数は空のままにします。

なぜ要素数も一緒に渡すのか

ここが最大のポイントです。関数の中では、受け取った配列が何個の要素を持つのか分かりません。sizeof(a) / sizeof(a[0]) を関数の中で書いても、正しい要素数にはなりません。だから個数を第2引数で一緒に渡すのが、Cの標準的な作法です。

「配列を渡すときは、個数もセットで渡す」。これは決まり文句として覚えてください。

なお int a[] の代わりに int a[5] と書くこともできますが、コンパイラはこの 5 を確認しません。書いてあっても守られない数字は、かえって誤解のもとです。素直に int a[] と書き、個数は引数で渡しましょう。

書き換えると、呼び出し元も変わる

t07-07で「引数はコピーが渡る」と学びました。ところが配列は様子が違います。

#include <stdio.h>

void double_all(int a[], int n)
{
    for (int i = 0; i < n; i++) {
        a[i] *= 2;              // 関数の中で書き換える
    }
}

int main(void)
{
    int a[3] = {1, 2, 3};
    double_all(a, 3);
    printf("%d %d %d\n", a[0], a[1], a[2]);   // 2 4 6 ← 変わっている!
    return 0;
}

main の配列がしっかり書き換わりました。値渡しの話と矛盾しているように見えますが、そうではありません。配列そのものはコピーされておらず、渡っているのは「配列が置いてある場所」だからです。関数は、その場所へ出向いて中身を書き換えています。

100万要素の配列を毎回コピーしていたら遅くて仕方がない、という現実的な理由もあります。値渡しの原則は生きたまま、渡っている中身が違う——正体は第2部で明かします。

読むだけなら const を付ける

書き換えない配列には const を付けておくと、うっかり書き換えたときにコンパイラが教えてくれます。

int sum_of(const int a[], int n)

戻り値では配列を返せない

関数から配列そのものを返すことはできません。関数の中で作った配列は、return した瞬間にフレームごと片づけられてしまうからです(t07-06の話です)。そのためCでは、「呼び出し元が用意した配列を渡し、関数はそこへ書き込む」という形をよく使います。上の double_all がまさにその形で、標準ライブラリの関数にも同じ設計が並んでいます。

罠・注意

師範渡っておるのは中身ではなく、置き場所じゃ。理由は洞窟で分かる

まとめ: 配列は「名前だけ」を渡し、要素数を必ず添える。関数の中での書き換えは呼び出し元に届きます。

アプリでこのトピックを学ぶ(無料・登録不要)

確認問題

確認問題 1

配列を関数に渡すときは、配列の名前と一緒に ____ も渡すのが定石です。理由は、関数の中では受け取った配列の大きさが分からないからです。

  1. 要素数
  2. 配列の型名
  3. 配列の合計値
  4. 配列の先頭の値
答えを見る

正解:A(要素数)

関数の中で sizeof を使っても正しい要素数は得られません。そのため int sum_of(int a[], int n) のように、個数を第2引数で受け取るのがCの標準的な作法です。個数がずれると即座に境界外アクセスになります。

確認問題 2

「配列 src の中身を dst にコピーして表示して」と頼んだら、アイがこう書きました。アイの答案にバグはありますか。

1#include <stdio.h>
2
3void copy_all(const int src[], int dst[], int n)
4{
5    for (int i = 0; i < n; i++) {
6        dst[i] = src[i];
7    }
8}
9
10int main(void)
11{
12    int src[3] = {1, 2, 3};
13    int dst[3];
14
15    copy_all(src, dst, 3);
16    for (int i = 0; i < 3; i++) {
17        printf("%d ", dst[i]);
18    }
19    printf("\n");
20    return 0;
21}
答えを見る

判定:バグなし

バグはありません。添字は 0〜2 の範囲に収まっており、要素数3も正しく渡しています。読むだけの src に const が付いているのも良い書き方です。dst は初期化していませんが、使う前にすべての要素へ代入しているので問題ありません。アイの答案は疑ってかかるのが基本ですが、正しいものを「正しい」と判定できることも同じくらい大事です。

「配列」の目次

  1. 配列とは——箱の行列
  2. 添字と0始まりの理由
  3. 配列とループの黄金コンビ
  4. 境界外アクセスという重大事故
  5. 配列の初期化いろいろ
  6. 多次元配列
  7. 配列を関数に渡す——入口編
  8. 集計・最大値・検索の定石

もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。

完全版の販売ページは準備中です。

ほかのトラック

📚 姉妹教材:手を動かして覚える Linux 教材 — Linuxとインフラの仕組みを地図で学ぶ

Web版