コース目次 / 第2章

配列とポインタ演算

配列の添字アクセスnums[1]が、実はポインタ演算*(nums+1)と同じであることを、アドレスの差を測って確認します。ポインタ演算が型のサイズ単位で進むことを理解します。

第2章 / 全6章目安 約9分この章のゴール: 配列アクセスとポインタ演算が同じものだと理解し、要素のアドレス間隔を確認できる

配列は、C言語では特別な魔法ではありません。前章で学んだ「アドレス+バイト数」の延長で、そのまま理解できます。

配列は、連続したメモリの並び

#include <stdio.h>

int main(void) {
    int nums[3] = {10, 20, 30};
    printf("nums[1] = %d\n", nums[1]);
    printf("*(nums+1) = %d\n", *(nums + 1));
    printf("nums のアドレス:   %p\n", (void*)nums);
    printf("nums+1 のアドレス: %p\n", (void*)(nums + 1));
    printf("差: %ld バイト\n", (char*)(nums+1) - (char*)nums);
    return 0;
}
gcc -o arr arr.c && ./arr
nums[1] = 20
*(nums+1) = 20
nums のアドレス:   0x7fff8fd9cdcc
nums+1 のアドレス: 0x7fff8fd9cdd0
差: 4 バイト

nums[1] と (nums + 1) は、まったく同じ値を返しました。実は、これは偶然ではありません——C言語では nums[1] は単に (nums + 1) の読みやすい書き方にすぎません。配列名 nums は、多くの場面で「先頭要素へのポインタ」として振る舞います。

ポインタ演算は、型のサイズ単位で進む

もう一つ、大事な発見があります。nums + 1 は、アドレスとして「1」だけ進んだのではなく、「4バイト(intのサイズ)」進んでいます。

ポインタに +1 すると、指している型のサイズぶんアドレスが進みます。int* なら4バイト、char* なら1バイト、double* なら8バイト。
これは、nums[2](3番目の要素)が正しく取り出せるようにするための、言語の設計です。もし +1 が常に1バイトしか進まなかったら、配列の2番目の要素を取り出すのに、いちいち型のサイズを掛け算しなければなりません。

型を通して、メモリを覗く

C言語のポインタは、「このアドレスから、この型として、この長さで読め」という指示でもあります。同じメモリでも、どの型のポインタとして見るかで、読まれ方が変わります。

#include <stdio.h>

int main(void) {
    int x = 0x41424344;         // メモリ上には 4バイトの並びとして置かれる
    unsigned char *bytes = (unsigned char*)&x;
    for (int i = 0; i < 4; i++) {
        printf("バイト%d: 0x%02x\n", i, bytes[i]);
    }
    return 0;
}

int として見れば1つの数値 0x41424344 ですが、unsigned char*(1バイトずつ読むポインタ)にキャストして見ると、同じメモリが4つのバイトの並びとして見えます(多くの環境ではリトルエンディアンなので 44 43 42 41 の順に並びます)。型は、同じメモリをどう解釈するかのレンズにすぎません。この見方は、次の章の文字列(バイトの並びとして扱う代表例)にそのままつながります。

持ち帰る一言

配列アクセスは、ポインタ演算の読みやすい書き方。 nums[1] は *(nums+1) と同じで、ポインタ演算は型のサイズ単位で進む。型は、同じメモリをどう読むかのレンズにすぎない。次は、この延長にある「文字列」——そこに、C言語最大の落とし穴があります。

こうなっていればOK

卒業まであと3章です。

この章はまだ完了していません。