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

9章では、いよいよ C 言語の「ポインタ」が本格的に登場しましたね。
ポインタは “変数の住所を扱う仕組み” であり、C 言語を深く理解するうえで欠かせないテーマです。

「アドレスって何?」「変数の場所を知ると何が嬉しいの?」
そんな疑問を抱えたまま学ぶ人も多いですが、安心してください。

この記事では、9章で学んだ内容を やさしく思い出しながら 練習問題にチャレンジできるよう、ポインタの基本やアドレスの仕組みを図や表も使って丁寧に説明していきます。

ポインタが扱う「アドレス」とは?

アドレスとは「メモリ上で値が保存されている場所」のことです。
例えば、変数 x が保存されている場所に番地(例:0x7ffeefb4a5ec)が割り当てられています。

下の図のように「箱(変数)」の位置を表すのがポインタです。

メモリとアドレスのイメージ

0123
0x100010
0x100420
0x1008A
0x100C― 空き領域 ―― 空き領域 ―― 空き領域 ―― 空き領域 ―
アドレス内容(変数や値)
0x1000int a = 10
0x1004int b = 20
0x1008char c = 'A'
0x100C― 空き領域 ―

ポインタ変数には、この「アドレス」がそのまま入ります。

ポインタ関連の命令(書式と働き)

命令書式何をする?
アドレス演算子&変数変数のアドレス(住所)を取得する。
間接演算子*ポインタ変数ポインタが指している先の中身を読む。
ポインタ型の宣言型名* 変数名指定した型のアドレスを格納できる。

int n = 50;
int* p = &n;    // p には n のアドレスが入る
printf("%d\n", *p);   // 間接参照で n の中身(50)を表示

9章の練習問題

以下では、サンプル問題を参考にしつつ、より理解が深まるよう類似問題を5つ作成しました。
すべて 解答例+解説つき です。

【練習9-1】(型選択:ポインタの基本)

次の情報を格納する変数に最も適した型を答えてください。

(1) 半角文字1つを格納する変数 symbol
(2) 最大でも 50 万までの売上金額を格納する変数 sales
(3) (1) の symbol のアドレスを格納する変数 symbolAddr
(4) (2) の sales のアドレスを格納する変数 salesAddr
(5) Player 型変数 p の先頭アドレスを格納する変数 playerAddr
(6) 要素数 20 の short 型配列 temps の先頭アドレスを格納する変数 tempAddr
(7) 正体不明のアドレスを受け取る変数 unknownAddr
  (その先に何のデータ型があるかは気にしない)

【解答例】

番号変数名適切な型解説
(1)symbolchar文字 1 つは char
(2)salesint最大 50 万なら int で十分
(3)symbolAddrchar*char のアドレスを格納する
(4)salesAddrint*int のアドレスを格納する
(5)playerAddrPlayer*構造体 Player のアドレス
(6)tempAddrshort*short 配列の先頭=short*
(7)unknownAddrvoid*型不明のアドレス → void*

【練習9-2】(sizeof とポインタのサイズ)

以下の変数のサイズを sizeof を使って調べてください。

(1) char symbol
(2) int sales
(3) char* symbolAddr
(4) int* salesAddr

また、(1)(2) はサイズが異なるのに対して、(3)(4) のサイズが同じ理由を説明してください。

解答例

一般的なサイズ(64bit環境例)
char1 バイト
int4 バイト
char*8 バイト
int*8 バイト

なぜポインタ(symbolAddr と salesAddr)は同じサイズなのか?

ポインタは 「データの型ではなく、アドレスそのもの」 を扱うため、
格納するのは メモリ上の番地(数値) です。

そのため、

  • char のポインタ
  • int のポインタ
  • struct のポインタ

これらは すべて同じサイズのアドレスを扱う ので、
ポインタの大きさは一定(多くの環境で 8 バイト)となります。

【練習9-3】(アドレスを受け取って中身を表示する関数)

「int 型変数のアドレス」を受け取り、その先の値を表示する printIntByAddress 関数を作成し、main から呼び出してください。

【解答例】

プロジェクト名:9-14-1 ソースファイル名: sample9-14-1.c

#include <stdio.h>

void printIntByAddress(int* addr)
{
    printf("値:%d\n", *addr);
}

int main(void)
{
    int num = 42;
    printIntByAddress(&num);
    return 0;
}

【解説】

  • 引数 int* addr は int のアドレス
  • *addr により値を取得できる(間接演算子)

【練習9-4】(スタック領域とアドレス変化の観察)

次のプログラムを実行したあと、問いに答えてください。

プロジェクト名:9-14-2 ソースファイル名: sample9-14-2.c

#include <stdio.h>

void subB(void) {
    int y = 200;
    printf("y-address: %ld\n", (long)&y);
}

void subA(void) {
    int x = 100;
    printf("x-address: %ld\n", (long)&x);
    subB();
}

int main(void) {
    subA();
    return 0;
}

【質問】
(1) 変数 x と y は、メモリ内の何という領域に確保されますか。
(2) どちらの変数のほうが小さい番地になることが多いですか。
(3) (2) の結果から、その領域はアドレスのどちら向きに伸びると推測できますか。

【解答例】
(1) どちらも スタック領域 に確保される。
(2) 一般的には 後から作られた y のアドレスがより小さくなる
(3) スタックは 高い番地 → 低い番地へ向かって利用される

【練習9-5】(ポインタを使った入れ替え関数)

2つの int 型変数のアドレスを受け取り、
中身を入れ替える swapInt 関数を作成して動作を確認してください。

【解答例】

プロジェクト名:9-14-3 ソースファイル名: sample9-14-3.c

#include <stdio.h>

void swapInt(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void)
{
    int m = 5, n = 9;
    swapInt(&m, &n);

    printf("m=%d, n=%d\n", m, n);
    return 0;
}