コンテンツにスキップ

よくわかるポインタ

Step1

まずは、ポインタの基本(1)を見ましょう。 値がどのようにメモリに配置されているか分かりましたかね。

動画にあった、値の格納が型によって違うというのを以下のコードで確認してみましょう。

#include <stdio.h>

int main() {
    float a = 3.0;
    int b = 3;

    // intもfloatも4byte
    printf("%lu %lu\n", sizeof(int), sizeof(float));
    // 中に入ってるのは、3
    printf("%0.1f %d\n", a, b);
    // 実際の中身のbitは違う
    printf("%x\n", a);
    printf("%x\n", b);

    // bのbitをfloatとして表示すると小さすぎて、0
    printf("%f\n", *(float *)&b);
    // aのbitをintとして表示
    printf("%d\n", *(int *)&a);

    return 0;
}

floatが実際どのようにbitになってるかは、ここをみると良いでしょう。

Step2

ポインタの基本(2)を見ましょう。 ポインタのときに使う演算子、*&について理解できましたか?

#include <stdio.h>

int main() {
    int v = 0;
    int *p;

    p = &v;
    *p = 3;
    printf("v = %d\n", v);
    printf("*p = %d\n", *p);

    // 実際にアドレスを表示してみる
    printf("%p\n", p);

    return 0;
}

Step3

ポインタを関数の引数に渡すを見ましょう。 swapはポインタを説明するときによく使う関数です。

ポインタを理解するときは、動画にあるように図を使うと理解が進みます。 以下のコードでswapとアドレスがちゃんと渡されているのを確認しましょう。

#include <stdio.h>

void swap(int *p, int *q) {
    printf("&x = %p\n", p);

    int tmp = *p;
    *p = *q;
    *q = tmp;
}

int main() {
    int x = 2, y = 3;

    printf("&x = %p\n", &x);

    printf("x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);

    return 0;
}

Step4

配列は、メモリ領域に連続してデータが格納されます。 その様子を以下のコードで確認しましょう。 配列の要素が1増えるたびに、アドレスが4増えています。これは、配列がint型の配列だからです。

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

int main() {
    int a[5];

    for (int i = 0; i < 5; i++) {
        a[i] = i + 1;
    }

    for (int i = 0; i < 5; i++) {
        printf("a[%d] = %d; &a[%d] = %p\n", i, a[i], i, &a[i]);
    }

    return 0;
}

配列をintからcharへ変えた、以下のコードを実行してみましょう。 先ほどと異なり、配列の要素が1増えるたびに、アドレスが1増えています。

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

int main() {
    char a[5];

    for (int i = 0; i < 5; i++) {
        a[i] = 'A' + i;
    }

    for (int i = 0; i < 5; i++) {
        printf("a[%d] = %c; &a[%d] = %p\n", i, a[i], i, &a[i]);
    }

    return 0;
}

Step5

メモリ領域の確保には、静的確保動的確保があります。

静的確保とはプログラムのコンパイル時にどのくらいメモリを使用するかわかっている場合です。 この場合は、int a[5];のようにプログラムに書いて、メモリ領域を確保します。

動的確保とはプログラムのコンパイル時に使用するメモリ量がわからない場合です。 この場合は、mallocを使います。 以下のプログラムを実行してみましょう。

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

// ./a.out 5 のように引数を与えて実行しましょう。
int main(int argc, char *argv[]) {

    int n = atoi(argv[1]); // 実行するまでどれだけメモリを必要としているかわからない。
    int *p = malloc(sizeof(int) * n);

    for (int i = 0; i < n; i++) {
        *(p + i) = i;
    }

    for (int i = 0; i < n; i++) {
        printf("*(p + %d) = %d; p + %d = %p\n", i, *(p + i), i, p + i);
    }

    return 0;
}

mallcのプログラムとStep 4の配列のプログラムが似ていることに気づきましたか? 配列のプログラムをポインタを使って書き直してみると、類似性に気づきやすいでしょう。

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

int main() {
    // aが配列の先頭アドレス
    int a[5];

    for (int i = 0; i < 5; i++) {
        *(a + i) = i + 1;
    }

    for (int i = 0; i < 5; i++) {
        printf("a[%d] = %d; &a[%d] = %p\n", i, *(a + i), i, a + i);
    }

    return 0;
}