8-4 ポインタと文字列の関係

この記事は約10分で読めます。

文字列とポインタ

文字列リテラルはchar型配列の定数として処理されます。配列ということは、ポインタを使用すると途中の要素にアクセスできます。

#include <stdio.h>

int main(void){
	char str[]="abcdef";
	char *ptr;
	ptr=str;
	printf("str:%s\n",str);
	printf("ptr:%s\n",ptr);
	printf("str=%c\n",*str);
	printf("str[0]=%c\n",str[0]);
	printf("ptr[0]=%c\n",ptr[0]);
	return 0;
}

実行結果

str:abcdef
ptr:abcdef
str=a
str[0]=a
ptr[0]=a

このプログラムでは、ptrに配列strのアドレスを代入しました。ptrはstrと同じ配列を指すことになります。%cはchar型の文字を一つ表示させるときのフォーマット指定子です。

配列とポインタの違い

次のプログラムを実行するとエラーとなります。

#include <stdio.h>

int main(void){
#include <stdio.h>

int main(void){
	char str[]="abcdef";
	char *ptr;
	ptr=str;
	printf("str=%p,str[0]=%c\n",str,str[0]);
	printf("ptr=%p,ptr[0]=%c\n",ptr,ptr[0]);
	printf("str:%s\n",str);
	printf("ptr:%s\n",ptr);
	str++;
	ptr++;
	printf("str=%p,str[0]=%c\n",str,str[0]);
	printf("ptr=%p,ptr[0]=%c\n",ptr,ptr[0]);
	printf("str:%s\n",str);
	printf("ptr:%s\n",ptr);
	return 0;
}

実行結果

コンパイルエラーにより実行できません
エラー表示(筆者の環境の場合)
エラー 11行目 左辺値が必要(関数 main )

どこが誤りかお分かりでしょうか?

ptrはポインタ型で、アドレスを格納する変数なのでptr++とするとsizeof charの分だけ増えたアドレスが代入されます。それに対して、strはメモリ上のchar配列”abcdef\0″を指す定数ポインタです。定数なのでstrに値を代入することはできません。

#include <stdio.h>

int main(void){
	char str[]="abcdef";
	char *ptr;
	ptr=str;
	printf("str=%p,str[0]=%c\n",str,str[0]);
	printf("ptr=%p,ptr[0]=%c\n",ptr,ptr[0]);
	printf("str:%s\n",str);
	printf("ptr:%s\n",ptr);
	ptr++;
	printf("str=%p,str[0]=%c\n",str,str[0]);
	printf("ptr=%p,ptr[0]=%c\n",ptr,ptr[0]);
	printf("str:%s\n",str);
	printf("ptr:%s\n",ptr);
	return 0;
}

実行結果

str=0019FF34,str[0]=a
ptr=0019FF34,ptr[0]=a
str:abcdef
ptr:abcdef
str=0019FF34,str[0]=a
ptr=0019FF35,ptr[0]=b
str:abcdef
ptr:bcdef

ptrに格納されているアドレスがsizeof charだけ増えたので、ptrがstr[1]を指すようになりました。このとき、ptrはstrの2つ目以降の要素の配列となります。

ポインタのポインタと多重配列

ポインタ型変数も変数なので、ポインタの配列やポインタのポインタを考えることができます。ポインタのポインタ変数を定義するには

型名 **変数名

とします。ポインタの配列は

型名 *配列名[]

と定義します。

#include <stdio.h>

int main() {
	int i,j;
	char *strary[] = {"aiueo","kakikukeko","sashisuseso"};
	for (i=0;i<3;i++){
		j=0;
		while (strary[i][j]){
			printf("strary[%d][%d]=%c,アドレス%p,先頭要素との差%d\n",i,j,strary[i][j],&strary[i][j],&strary[i][j]-&strary[0][0]);
			j++;
		}
		puts("");
	}
	return 0;
}

実行結果

strary[0][0]=a,アドレス0040A134,先頭要素との差0
strary[0][1]=i,アドレス0040A135,先頭要素との差1
strary[0][2]=u,アドレス0040A136,先頭要素との差2
strary[0][3]=e,アドレス0040A137,先頭要素との差3
strary[0][4]=o,アドレス0040A138,先頭要素との差4

strary[1][0]=k,アドレス0040A13A,先頭要素との差6
strary[1][1]=a,アドレス0040A13B,先頭要素との差7
strary[1][2]=k,アドレス0040A13C,先頭要素との差8
strary[1][3]=i,アドレス0040A13D,先頭要素との差9
strary[1][4]=k,アドレス0040A13E,先頭要素との差10
strary[1][5]=u,アドレス0040A13F,先頭要素との差11
strary[1][6]=k,アドレス0040A140,先頭要素との差12
strary[1][7]=e,アドレス0040A141,先頭要素との差13
strary[1][8]=k,アドレス0040A142,先頭要素との差14
strary[1][9]=o,アドレス0040A143,先頭要素との差15

strary[2][0]=s,アドレス0040A145,先頭要素との差17
strary[2][1]=a,アドレス0040A146,先頭要素との差18
strary[2][2]=s,アドレス0040A147,先頭要素との差19
strary[2][3]=h,アドレス0040A148,先頭要素との差20
strary[2][4]=i,アドレス0040A149,先頭要素との差21
strary[2][5]=s,アドレス0040A14A,先頭要素との差22
strary[2][6]=u,アドレス0040A14B,先頭要素との差23
strary[2][7]=s,アドレス0040A14C,先頭要素との差24
strary[2][8]=e,アドレス0040A14D,先頭要素との差25
strary[2][9]=s,アドレス0040A14E,先頭要素との差26
strary[2][10]=o,アドレス0040A14F,先頭要素との差27

このプログラムではchar*の配列straryを定義し、strary[0]が文字列”aiueo”の先頭を指すように初期化しました。同じようにstrary[1]は”kakikukeko”を、strary[2]は”sashisuseso”を指します。このとき、3つの文字列はメモリ上の連続した領域に格納されます。先頭要素とのアドレスの差は連続的に増えています。先頭要素との差が5、16のようそはNULLなのでwhile文にはじかれ、表示されません。

#include <stdio.h>

int main() {
	int i,j;
	char strary[][12] = {"aiueo","kakikukeko","sashisuseso"};
	for (i=0;i<3;i++){
		j=0;
		while (strary[i][j]){
			printf("strary[%d][%d]=%c,アドレス%p,先頭要素との差%d\n",i,j,strary[i][j],&strary[i][j],&strary[i][j]-&strary[0][0]);
			j++;
		}
		puts("");
	}
	return 0;
}

実行結果

strary[0][0]=a,アドレス0019FF18,先頭要素との差0
strary[0][1]=i,アドレス0019FF19,先頭要素との差1
strary[0][2]=u,アドレス0019FF1A,先頭要素との差2
strary[0][3]=e,アドレス0019FF1B,先頭要素との差3
strary[0][4]=o,アドレス0019FF1C,先頭要素との差4

strary[1][0]=k,アドレス0019FF24,先頭要素との差12
strary[1][1]=a,アドレス0019FF25,先頭要素との差13
strary[1][2]=k,アドレス0019FF26,先頭要素との差14
strary[1][3]=i,アドレス0019FF27,先頭要素との差15
strary[1][4]=k,アドレス0019FF28,先頭要素との差16
strary[1][5]=u,アドレス0019FF29,先頭要素との差17
strary[1][6]=k,アドレス0019FF2A,先頭要素との差18
strary[1][7]=e,アドレス0019FF2B,先頭要素との差19
strary[1][8]=k,アドレス0019FF2C,先頭要素との差20
strary[1][9]=o,アドレス0019FF2D,先頭要素との差21

strary[2][0]=s,アドレス0019FF30,先頭要素との差24
strary[2][1]=a,アドレス0019FF31,先頭要素との差25
strary[2][2]=s,アドレス0019FF32,先頭要素との差26
strary[2][3]=h,アドレス0019FF33,先頭要素との差27
strary[2][4]=i,アドレス0019FF34,先頭要素との差28
strary[2][5]=s,アドレス0019FF35,先頭要素との差29
strary[2][6]=u,アドレス0019FF36,先頭要素との差30
strary[2][7]=s,アドレス0019FF37,先頭要素との差31
strary[2][8]=e,アドレス0019FF38,先頭要素との差32
strary[2][9]=s,アドレス0019FF39,先頭要素との差33
strary[2][10]=o,アドレス0019FF3A,先頭要素との差34

straryをcharの2重配列にして書き直しました。2重配列では2つ目の要素数は省略できないので、11にしました。

先頭要素との差を見ると、4から12に増え、21から24に増えています。これは3つの文字列が強制的にchar[12]としてメモリ確保されているからです。

まとめ

文字列リテラルはconst charの配列であるから、配列を指すポインタを使用するとき、低数値を書き換えないように気をつける必要がある。

二重配列では、ポインタへのポインタを使用する。

コメント