ポインタと配列の関係

Copyright(C) 14May2003 coskx

ポインタ変数と配列は関係が深い。このページでは以下のことを示す。
(1)ポインタ変数が配列として記述できることを示す。
(2)
ポインタ変数にはアドレスが格納されるのに,どうして「int型の変数のアドレスを格納するポインタ変数」や「char型の変数のアドレスを格納するポインタ変数」などの種類があるのかを示す。


1.ポインタ変数は配列と同じ記述ができる??


1.ポインタ変数は配列記述ができる??

次のプログラムの動作をたどりなさい。

List 1 ポインタ変数が「配列」に変身

#include <stdio.h>

int main()
{
    char str[20]="ABCDEFG";
    char *ptr=str; /*char型の変数を指すポインタ変数ptrを宣言し,ptrに文字型配列str[]の先頭アドレスを代入*/
    int i;
    printf("初期状態\n");
    printf("str=%p &str[0]=%p ptr=%p\n",str,&str[0],ptr);
    printf("第1ループ\n");
    for (i=0;i<5;i++) {
        printf("i=%d &str[i]=%p str[i]=%c\n",i,&str[i],str[i]);
    }
    printf("第2ループ\n");
    for (i=0;i<5;i++) {
        printf("ptr=%p *ptr=%c\n",ptr,*ptr);
        ptr++;
    }
    printf("第3ループ\n");
    ptr=str; /*ptrに文字型配列str[]の先頭アドレスを代入*/
    for (i=0;i<5;i++) {
        printf("ptr=%p i=%d (ptr+i)=%p *(ptr+i)=%c\n",ptr,i,ptr+i,*(ptr+i));
    }
    printf("第4ループ\n");
    for (i=0;i<5;i++) {
        printf("ptr=%p i=%d ptr[i]=%c\n",ptr,i,ptr[i]);
    }
    return 0;
}

実行結果
初期状態
str=0012FF68 &str[0]=0012FF68 ptr=0012FF68
第1ループ
i=0 &str[i]=0012FF68 str[i]=A
i=1 &str[i]=0012FF69 str[i]=B
i=2 &str[i]=0012FF6A str[i]=C
i=3 &str[i]=0012FF6B str[i]=D
i=4 &str[i]=0012FF6C str[i]=E
第2ループ
ptr=0012FF68 *ptr=A
ptr=0012FF69 *ptr=B
ptr=0012FF6A *ptr=C
ptr=0012FF6B *ptr=D
ptr=0012FF6C *ptr=E
第3ループ
ptr=0012FF68 i=0 (ptr+i)=0012FF68 *(ptr+i)=A
ptr=0012FF68 i=1 (ptr+i)=0012FF69 *(ptr+i)=B
ptr=0012FF68 i=2 (ptr+i)=0012FF6A *(ptr+i)=C
ptr=0012FF68 i=3 (ptr+i)=0012FF6B *(ptr+i)=D
ptr=0012FF68 i=4 (ptr+i)=0012FF6C *(ptr+i)=E
第4ループ
ptr=0012FF68 i=0 ptr[i]=A
ptr=0012FF68 i=1 ptr[i]=B
ptr=0012FF68 i=2 ptr[i]=C
ptr=0012FF68 i=3 ptr[i]=D
ptr=0012FF68 i=4 ptr[i]=E

「初期状態」は「str」が「&str[0]」と同じ文字列の先頭番地をあらわしていることの確認である。

「第1ループ」は通常のchar型変数の配列を順に表示したものである。

「第2ループ」はポインタ変数を増加させながら,ポインタ変数が指している要素を順に表示したものである。

「第3ループ」はポインタ変数を固定し,これに定数を加えて,ptrから先のiバイト目を指すアドレス値をつくり,そこにある要素を表示したものである。

「第4ループ」はポインタ変数に「[]」をつけて表示したものである。
ptrがポインタ変数でiがint型変数の時「ptr[i]」の表現は「*(pyr+i)」と同じ意味になる。
(「ptr[3]」の表現は「*(pyr+3)」と同じ意味になる。)

動的メモリ確保の時に,ポインタである変数が,途中から配列として扱えるようになったのは,「第4ループ」と同じように,ポインタ変数+[]の表現を使用したためである。


2.ポインタ変数になぜ指す変数の型が必要なのか??


ポインタ変数はアドレスを格納することができる変数であるが,ポインタ変数は
    char *ptr;
    short int *ptr1;
    long int *ptr2;
のように「〜型を指すポインタ変数(〜型の変数のアドレスを格納する変数)」で分類される。
そして
    char str[50]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
のようなchar型配列があると,char型を指すポインタ変数に対しては
    ptr=str;
は可能で,ptrにはstr[]の先頭アドレスが格納されるが,そうでないポインタ変数に対しては
    ptr1=str;
    ptr2=str;
は文法上に許されない。ptr1にもptr2にもstr[]の先頭アドレスが格納できない。むりやり格納するにはキャストして
    ptr1=(short int *)str;
    ptr2=(long int *)str;
のようにすると可能になる。

そのあと様子は次のプログラムを追跡しなさい。

List 2 ポインタ変数の種類

#include <stdio.h>

int main()
{
    char str[50]="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char *ptr=str; /*char型の変数を指すポインタ変数ptrを宣言し,ptrに文字型配列str[]の先頭アドレスを代入*/
    short int *ptr1=(short int *)str;
    long int *ptr2=(long int *)str;
    int i;
    printf("変数のバイト数の確認\n");
    printf("sizeof(char)     =%d\n",sizeof(char));
    printf("sizeof(short int)=%d\n",sizeof(short int));
    printf("sizeof(long int) =%d\n",sizeof(long int));
    printf("初期状態\n");
    printf("  str=%p\n",str);
    printf("  ptr=%p   ptr1=%p   ptr2=%p\n",str,ptr,ptr1,ptr2);
    printf("検査\n");
    printf("ptr+1=%p ptr1+1=%p ptr2+1=%p\n",ptr+1,ptr1+1,ptr2+1);
    printf("ptr+2=%p ptr1+2=%p ptr2+2=%p\n",ptr+2,ptr1+2,ptr2+2);
    printf("第1ループ\n");
    for (i=0;i<5;i++) {
        printf("ptr=%p *ptr=%x\n",ptr,*ptr); /*「%x」は十六進数表示*/
        ptr++;
    }
    printf("第2ループ\n");
    for (i=0;i<5;i++) {
        printf("ptr1=%p *ptr1=%x\n",ptr1,*ptr1); /*「%x」は十六進数表示*/
        ptr1++;
    }
    printf("第3ループ\n");
    for (i=0;i<5;i++) {
        printf("ptr2=%p *ptr2=%x\n",ptr2,*ptr2); /*「%x」は十六進数表示*/
        ptr2++;
    }
    return 0;
}

実行結果 Windows PCの例 (他のコンピュータでは第2・第3ループのデータ表示が異なる場合がある)

変数のバイト数の確認
sizeof(char)     =1
sizeof(short int)=2
sizeof(long int) =4
初期状態
  str=0012FF48
  ptr=0012FF48   ptr1=0012FF48   ptr2=0012FF48
検査
ptr+1=0012FF49 ptr1+1=0012FF4A ptr2+1=0012FF4C
ptr+2=0012FF4A ptr1+2=0012FF4C ptr2+2=0012FF50
第1ループ
ptr=0012FF48 *ptr=41
ptr=0012FF49 *ptr=42
ptr=0012FF4A *ptr=43
ptr=0012FF4B *ptr=44
ptr=0012FF4C *ptr=45
第2ループ
ptr1=0012FF48 *ptr1=4241
ptr1=0012FF4A *ptr1=4443
ptr1=0012FF4C *ptr1=4645
ptr1=0012FF4E *ptr1=4847
ptr1=0012FF50 *ptr1=4a49
第3ループ
ptr2=0012FF48 *ptr2=44434241
ptr2=0012FF4C *ptr2=48474645
ptr2=0012FF50 *ptr2=4c4b4a49
ptr2=0012FF54 *ptr2=504f4e4d
ptr2=0012FF58 *ptr2=54535251

「変数のバイト数の確認」のところで,各変数のコンピュータ内部表現が何バイトに対応しているかが表示されている。

変数の型 コンピュータ内部表現でのバイト数

char

1

short int

2

long int

4

「検査」のところで,「ptr=12FF48に対してptr+1=12FF49,ptr+2=0012FF4A」は1ずつ増加するので普通である。ところが
「ptr1=0012FF48 に対してptr1+1=0012FF4A,ptr1+1=0012FF4A」や「ptr2=0012FF48に対して ptr2+1=0012FF4C,ptr2+1=0012FF4C」は変な感じがする。ptr1は+1しているのに2ずつ増加し,ptr2は+1している のに4ずつ増加している。このことは後で考えることにする。

「第1ループ」はまさにこの通りでよい。'A'=41(十六進数),'B'=42,.....

「第2ループ」では*ptr1を表示したら,2バイトをまとめて表示してきた。4241は「BA」をそ のまま並べている。その後もptr1を1ずつ増加させているが,ptr1の値(アドレスをあらわしている)は2ずつ増加し,*ptr1の表示では,2バイ トずつ表示していることがわかる。→short int型の変数を指すことになっているポインタ変数ptr1は2バイト(short int型変数の内部表現バイト数)を指していて,アドレス値の増減が指示されると2ずつ変化する。

「第3ループ」では*ptr1を表示したら,4バイトをまとめて表示してきた。44434241は 「DCBA」をそのまま並べている。その後もptr2を1ずつ増加させているが,ptr2の値(アドレスをあらわしている)は4ずつ増加し,*ptr2の 表示では,4バイトずつ表示していることがわかる。。→long int型の変数を指すことになっているポインタ変数ptr2は4バイト(long int型変数の内部表現バイト数)を指していて,アドレス値の増減が指示されると4ずつ変化する。

ポインタ変数にはアドレスが保存されているが,ポインタ 変数は自分がどのような型の変数を指しているか(何バイトにわたって指しているか)を知っている。また,ポインタ変数が保持しているアドレスの増減が指示 されると,変数長(その変数が何バイトでできているか)ずつ増減する。これはポインタ変数宣言時に何型の変数を指すポインタなのか明示されているのでで可能になっている。
ポインタ変数はこのような振る舞いをするため,何型の変数を指すポインタ変数かを指定しなければならない。

なお,void型ポインタ変数は,型のないただのアドレスを格納するためのものである。
void型ポインタ変数は「++」などの演算が定義されないため,演算を行なおうとするとコンパイラがエラーを出す。