10.ポインタ

10-8. ポインタの加減算

◇ポインタの加減算
上記で利用したプログラム

#include<stdio.h>
main()
{
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2)
    printf("%s\n",str);
}

/* 結果-------------
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

についてポインタの加算部分を説明します。

ここではポインタstrはfor分で1回に2つ加算されています。
ポインタの値(str)はアドレスなのでアドレスが2つ増えた(正確には進んだ)事が分かります。つまり2バイトづつ進んでいるわけです。

(注意:アドレスの進み方は型の影響を受けます)

よって文字列の先頭が2バイト(全角1文字)分づつずれて[結果]のような表示ができたわけです。

それでは今度はポインタの減算を含めてみます。

#include<stdio.h>
main()
{
    int cnt=0;
    char *str ="Cプロダクション";
    for(;*str!='\0';str+=2,cnt++)
    printf("%s\n",str);
    for(;cnt>=0;str-=2,cnt--)
    printf("%s\n",str);
}

/* 結果
Cプロダクション
プロダクション
ロダクション
ダクション
クション
ション
ョン
ン

ン
ョン
ション
クション
ダクション
ロダクション
プロダクション
Cプロダクション
*/

printfの%sは文字列の先頭(ここではポインタの示すアドレス)から\0(null)までを表示する変換指定子なので上のような結果が得られます。

ポインターの加減算は参照するアドレス値の加減算を行うのですが、その単位が変数型によって影響を受けます。

つまり p+1 としてもアドレス値が1増えるとは限らないのです。それは次の例で確かめて見ましょう。

◇文字型の場合

まずは文字型の場合です。

#include<stdio.h>
main()
{
    char str[]="ABCDEFGHIJKLMN";
    char *p;

    for(p=str;*p!='\0';p++)printf("%p = %c\n",p,*p);
}

/* 結果
0064FDF4 = A
0064FDF5 = B
0064FDF6 = C
0064FDF7 = D
0064FDF8 = E
0064FDF9 = F
0064FDFA = G
0064FDFB = H
0064FDFC = I
0064FDFD = J
0064FDFE = K
0064FDFF = L
0064FE00 = M
0064FE01 = N
*/

これはちゃんと1バイトずつアドレス値がふえていますね。
pを1増やすたびアドレス値がchar一つ分ふえています。

◇整数型の場合

#include<stdio.h>
main()
{
	int arr[]={1,2,3,4,5,6,7,8,9,0};
	int *p;

	for(p=arr;*p!=0;p++)printf("%p = %d\n",p,*p);
}

/* 結果
0064FDDC = 1
0064FDE0 = 2
0064FDE4 = 3
0064FDE8 = 4
0064FDEC = 5
0064FDF0 = 6
0064FDF4 = 7
0064FDF8 = 8
0064FDFC = 9
*/

今度はアドレス値が4バイトずつ増えています。
p++なのでプログラム上ではアドレス値が1ずつ増えるような感じはしますが実際はintひとつ分アドレスが進んでいます。

以上をまとめるとポインタの演算は

p+N (Nは整数)の場合

現在のアドレス値 + N×sizeof(ポインタの宣言型)

になります。

◇配列との関係

さらに配列との関係をアドレス値を基準にして説明します。

#include<stdio.h>
main()
{
  int i,arr[] = {0,1,2,3,4,5,6,7,8,9};
  int *point;

  point = arr;
  for(i=0;i<10;i++){
    printf("&arr[%d] = %p arr[%d] = %d ",i,&arr[i],i,arr[i]);
    printf("point+%d = %p *(point+%d) = %d\n",i,point+i,i,*(point+i));
  }
}

/* 結果
&arr[0] = 0064FDDC arr[0] = 0 point+0 = 0064FDDC *(point+0) = 0
&arr[1] = 0064FDE0 arr[1] = 1 point+1 = 0064FDE0 *(point+1) = 1
&arr[2] = 0064FDE4 arr[2] = 2 point+2 = 0064FDE4 *(point+2) = 2
&arr[3] = 0064FDE8 arr[3] = 3 point+3 = 0064FDE8 *(point+3) = 3
&arr[4] = 0064FDEC arr[4] = 4 point+4 = 0064FDEC *(point+4) = 4
&arr[5] = 0064FDF0 arr[5] = 5 point+5 = 0064FDF0 *(point+5) = 5
&arr[6] = 0064FDF4 arr[6] = 6 point+6 = 0064FDF4 *(point+6) = 6
&arr[7] = 0064FDF8 arr[7] = 7 point+7 = 0064FDF8 *(point+7) = 7
&arr[8] = 0064FDFC arr[8] = 8 point+8 = 0064FDFC *(point+8) = 8
&arr[9] = 0064FE00 arr[9] = 9 point+9 = 0064FE00 *(point+9) = 9
*/

配列をポインタから参照した時の対応表みたいのを出してみました。
配列にアクセスする時のポインタの記述の参考になると思います。

前回の説明の通り、point+i の値は

pointの値(つまり配列arrの先頭アドレス)+型サイズ(int)×i

になっています。(ちゃんと16進数で4バイトづつ増えています。)
対応としては、

アドレス値 :&arr[i] = point+i
要素の値  :arr[i] = *(point+i)

の関係になっています。