C言語入門|10章の練習問題

10章では、ポインタと配列の境界をさらに深く学習し、
memcpy / memcmp配列を関数に渡す際の振る舞い
そして スタック vs ヒープのメモリ確保 など、
“実務で頻繁に使われる超重要テーマ” を扱いました。

特に、

  • 配列は関数に渡すとポインタになる。
  • memcpy はアドレスを起点にバイト単位でコピーする。
  • memcmp はバイト列の比較を行う。
  • 配列名と &配列名 と &配列[0] の違い。
  • malloc によるヒープ領域の確保

こういった「これまでのCの知識を結びつける概念」が一気に登場した章です。

この記事では、10章の理解を定着させるための練習問題を紹介しつつ、
図や表を使って配列・ポインタ・メモリの関係をやさしく説明します。

配列とポインタの関係

配列とアドレスの関係

インデックスメモリ内の値アドレス
ages[0]100x1000
ages[1]110x1001
ages[2]120x1002

ここで重要なポイント

  • ages は「ages[0] のアドレス」として扱われる。
  • &ages と &ages[0] は厳密には型が違うが、アドレス値は一致する。
  • ages[i] は *(ages + i) と同じ意味
  • i[ages] のように逆順でも実は動く(絶対に真似しないほうが良い)

標準関数 memcpy / memcmp の書式と役割

関数名書式何をする?
memcpymemcpy(コピー先, コピー元, バイト数)指定したバイト数を丸ごとコピーする。
memcmpmemcmp(比較A, 比較B, バイト数)A と B をバイト単位で比較し、同じなら 0 を返す。

これらは「配列を丸ごとコピーしたい」「配列同士を比較したい」というときの定番関数です。

スタックとヒープの違い

領域用途代表的な確保方法
スタック関数内の一時変数int a; / char name[10];
ヒープ長期間保持したいデータmalloc / free

10章の練習問題

【練習10-1】(memcpy・memcmp・配列渡しの性質)

次のプログラムについて、問いに答えてください。

プロジェクト名:10-12-1 ソースファイル名: sample10-12-1.c

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

void showValues(int nums[4])
{
    for (int i = 0; i < 4; i++) {
        printf("%d番目:%d\n", i + 1, nums[i]);
    }
}

int main(void)
{
    int x[] = {5, 10, 15, 20};
    int y[4];

    showValues(x);
    memcpy(y, x, sizeof(x));

    showValues(y);

    if (memcmp(x, y, sizeof(x)) == 0) {
        printf("コピーは正常です\n");
    }
    return 0;
}

(1)このプログラムで「特殊構文①と②」が使われている箇所をすべて挙げてください。
  (※ 配列名を暗黙にポインタへ変換して扱う部分を指す)

(2)特殊構文を使わず、より明確な記述に書き換えてください。
  showValues の引数で添え字演算子を使わないこと。

【解答例】

(1)特殊構文が使われている箇所

  • 関数 showValues(int nums[4])
    → 実際には int* nums と同じになる(配列→ポインタ化)
  • showValues(x)
    → x が「配列名」→「先頭アドレス」へ自動変換
  • memcpy(y, x, sizeof(x))
    → x が「&x[0]」と同等として扱われる
  • memcmp(x, y, sizeof(x))
    → 同上

(2)明確に表現した書き換え例

void showValues(int* nums)
{
    for (int i = 0; i < 4; i++) {
        printf("%d番目:%d\n", i + 1, *(nums + i));
    }
}

【練習10-2】(配列名とアドレスの比較)

配列 x[] を定義したとき、次のうち x と同じ意味になるもの をすべて選んでください。

(ア)*x
(イ)&x
(ウ)(x + 0)
(エ)&x[0]

【解答例】

選択肢同じ意味?解説
(ア)*x×x はアドレス、*x は中身(値)
(イ)&x×型も意味も違う
(ウ)*(x + 0)×値になるため ×
(エ)&x[0]x と同じアドレスを指す

【練習10-3】(ヒープ領域へのコピー)

以下の動作をするように main 関数を書き換えてください。

  • x はスタック配列(そのままでOK)
  • y はヒープ領域に 4 バイト確保する
  • memcpy で x → y へコピー
  • コピー後、値を確認して表示

【解答例】

プロジェクト名:10-12-2 ソースファイル名: sample10-12-2.c

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

void showValues(int* nums)
{
    for (int i = 0; i < 4; i++) {
        printf("%d番目:%d\n", i + 1, nums[i]);
    }
}

int main(void)
{
    int x[] = {5, 10, 15, 20};

    int* y = malloc(sizeof(x));  // ヒープに4要素分

    memcpy(y, x, sizeof(x));

    showValues(y);

    free(y);

    return 0;
}

【練習10-4】(N[p] を理解する)

次の配列があるとします。

int nums[3] = {8, 9, 10};

このとき、次のコードはコンパイルも実行も成功します。

printf("%d\n", 1[nums]);

これはなぜ可能なのか、配列アクセスの仕組みを説明してください。

【解答例と解説】

nums[i] は本来、

*(nums + i)

として処理されます。

この式は加算なので順序を入れ替えても同じ

*(i + nums)

それを角カッコで表せば i[nums] となる。

つまり i[nums] も nums[i] と全く同じ意味 になるため、
1[nums] で nums[1](=9)が表示できる。

ただし、可読性が極端に悪いため 実務では絶対に使わないこと