ポインタ変数の配列

Copyright(C) 14May2003 coskx

ポインタ変数も変数なので配列になる。ポインタ変数の配列の各要素にはアドレスが格納される。ただし,格納されるアドレスはすべて同じ型の変数を指していなければならない。
ここではポインタ配列の導入を解説する。


1.文字列と文字列を指すポインタ


次のプログラムは文字列(char型の配列)と文字列を指すポインタのテストプログラムである。

文字列配列と文字列を指すポインタ

#include <stdio.h>

int main()
{
    char str[]="ABCDEFG";                /*1*/
    char *pstr="ABC";                    /*2*/
    puts(str);                           /*3*/
    puts(pstr);                          /*4*/
    puts("PQR");                         /*5*/
    printf("str =%p\n",str);             /*6*/
    printf("pstr=%p\n",pstr);            /*7*/
    printf("\"ABCDEFG\"=%p\n","ABCDEFG");/*8*/
    printf("\"ABC\"=%p\n","ABC");        /*9*/
    printf("\"PQR\"=%p\n","PQR");        /*10*/
    printf("\"PQR\"=%p\n","PQR");        /*11*/
    return 0;
}

/****実行結果****
ABCDEFG
ABC
PQR
str =0012FF78
pstr=00406038
"ABCDEFG"=00406058
"ABC"=00406070
"PQR"=00406080
"PQR"=00406090
****************/


(1)/*1*/の「char str[]="ABCDEFG"」は,strという名前のchar型配列を次のように作る。
配列の大きさを明示していないが,コンパイラがちょうどよい大きさにしてくれる。
そして/*6*/のprintfで明らかなように,この配列の先頭番地は0012FF78である。
/*6*/のprintfで表示しているから,説明のために文字列配列の先頭番地を知ることができたが,通常はこのアドレスを知る必要はない。

char型配列 str 先頭アドレス:0012FF78

要素番号

0

1

2

3

4

5

6

7

'A'

'B'

'C'

'D'

'E'

'F'

'G'

'\0'


(2)/*2*/の「char *pstr="ABC"」はchar型をさすポインタ変数(char型のアドレスを保存するポインタ変数)pstrを宣言し,ポインタ変数に"ABC"(名前のない文字列)
の書いてあるアドレスを格納する。そして/*7*/のprintfで明らかなように,この配列の先頭番地は00406038である。この名前のない文字列は,プログラムが開始する前にメモリ上に存在している。
/*7*/のprintfで表示しているから,説明のためにポインタ変数pstrに保存されているアドレス(=文字列配列の先頭番地)を知ることができたが,通常はこのアドレスを知る必要はない。

char型を指すポインタ変数 pstr

00406038

名前のない文字列 先頭アドレス:00406038

要素番号

0

1

2

3

'A'

'B'

'C'

'\0'


通常"ABC"のように記述すると名前のない文字列が出来て,
"ABC"はその先頭アドレスを表すことになる。
それゆえ,
「char *pstr="ABC"」あるいは「char *pstr;  pstr="ABC"」は,""ABC"の先頭アドレスをchar型ポインタpstrに代入すると言う意味となる。
文字型配列の初期化(例えば「char str[]="ABCDEFG"」)では,配列の初期値の記述であり,特別な記述である。

/*5*/も"PQR"が名前のない文字列で,"PQR"がその先頭アドレスを表している例である。なぜなら,関数puts()はその引数がchar型のポインタであり,文字列の先頭番地を受け取るようになっている。

/*8*//*9*//*10*//*11*/もダブルクォーテーションで囲んだ文字列がその文字列の 存在する先頭アドレスを示している例である。/*10*//*11*/をみて分るように,同じ文字列"PQR"がメモリ中に2個所に存在する(異なる先頭 アドレスを持っている)ことがわかる。/*5*/の"PQR"も異なるアドレスに存在していると推測できる。

名前を持たない文字列配列は,printfなどで,Cプログラミングの最初から使っていた。
 printf("x = %d\n",x);
は,むりやり名前を持つ文字型配列を使って書いてみれば,
 char *syosiki = "x = %d\n";
 printf(syosiki,x);
のように書くことができる。これは,次のように書くこともできる。
 char syosiki[] = "x = %d\n";
 printf(syosiki,x);

ところで
 char str1[]="XYZ";
と初期値付きで変数宣言されたら,実行上では,str1[0]は'X'であり,
str1[1]は'Y'であり,str1[2]は'Z'である。
同様に
 char *str2="XYZ";

と初期値付きで変数宣言されたら,実行上では,*str2は'X'であり,*(str2+1)は'Y'であり,*(str2+2)は'Z'である。
配列のように表現することもできるので,str2[0]は'X'であり,str2[1]は'Y'であり,str2[2]は'Z'である。
名前を持たない文字列配列の場合でも同様に
"XYZ"[0]
は'X'であり,"XYZ"[1]は'Y'であり,"XYZ"[2]は'Z'である。
int型変数kが,0≦k≦2であれば
 printf("arerenore = %c\n", "XYZ"[k]);
のような表現もできる。




2.文字型の二次元配列と文字列を指すポインタの配列



次のプログラムは文字型の二次元配列と文字列を指すポインタ配列の紹介である。

sadsad

#include <stdio.h>

int main()
{
    char str[][10]={"ABCDE","XYZ","RSTU"};
    char *pstr[]={"ABC","PQRS","VWXYZ"}; /*これがポインタの配列*/

    puts(str[0]); /*
str[0]は文字列"ABCDE"の先頭アドレス*/
    puts(str[1]);
/*str[1]は文字列"XYZ"の先頭アドレス*/
    puts(str[2]);
/*str[2]は文字列"RSTU"の先頭アドレス*/

    puts(pstr[0]);
    puts(pstr[1]);
    puts(pstr[2]);

    printf("str[0]=%p\n",str[0]);
    printf("str[1]=%p\n",str[1]);
    printf("str[2]=%p\n",str[2]);

    printf("pstr[0]=%p\n",pstr[0]);
    printf("pstr[1]=%p\n",pstr[1]);
    printf("pstr[2]=%p\n",pstr[2]);
    return 0;
}

/****実行結果****
ABCDE
XYZ
RSTU
ABC
PQRS
VWXYZ
str[0]=0012FF60
str[1]=0012FF6A
str[2]=0012FF74
pstr[0]=0040605C
pstr[1]=00406068
pstr[2]=00406078
****************/


(1)「char str[][10]={"ABCDE","XYZ","RSTU"};」では次のような二次元配列が出来る。

文字型二次元配列 str 先頭アドレス:0012FF60

 

下位要素番号→

0

1

2

3

4

5

6

7

8

9

上位要素番号:0
この行の先頭
アドレス:0012FF60

値 →

'A'

'B'

'C'

'D'

'E'

'\0'

未使用

未使用

未使用

未使用

上位要素番号:1
この行の先頭
アドレス:0012FF6A

値 →

'X'

'Y'

'Z'

'\0'

未使用

未使用

未使用

未使用

未使用

未使用

上位要素番号:2
この行の先頭
アドレス:0012FF74

値 →

'R'

'S'

'T'

'U'

'\0'

未使用

未使用

未使用

未使用

未使用

また,str[0],str[1],str[2]はそれぞれアドレスを表し,そのアドレスは10ずつ増加している。
これは,二次元配列の各行が要素数10このchar型配列で出来ているからである。


(2)char *pstr[]={"ABC","PQRS","VWXYZ"}は,ポインタ変数の配列(要素数3)が出来て,3つの要素にそれぞれ文字列の先頭番地が格納されます。
ここで名前のない文字列が3つ出てくるが,これはプログラムが開始する前にメモリ上に存在している。
そして,それらの先頭アドレスがポインタ変数の配列の要素に格納される。

ポインタ変数の配列pstr 先頭アドレス:不明

要素番号

0

1

2

値(アドレス)

0040605C

00406068

00406078

名前のない文字列 先頭アドレス:0040605C

要素番号

0

1

2

3

'A'

'B'

'C'

'\0'

名前のない文字列 先頭アドレス:00406068

要素番号

0

1

2

3

4

'P'

'Q'

'R'

'S'

'\0'

名前のない文字列 先頭アドレス:00406078

要素番号

0

1

2

3

4

5

'V'

'W'

'X'

'Y'

'Z'

'\0'


 

3.char *argv[]とはなにか



プログラムの関数main()の引数としてコマンドラインを取得する表現があるが,
main(int argc,char *argv[])
となっている。ここでchar *argv[]についてその構造を明らかにしてみる。

たとえば,getclという実行可能なファイルがあった時,
>getcl hello world
のように起動したとすると次のような引数がスタートアップルーチンから関数main(int argc,char *argv[]))へ渡される。

int argc これには「3」が渡される。これは渡される文字列が3個あると言う意味

char *argv[] はポインタ変数の配列(char型変数へのポインタ変数の配列)なので,次のようになっている。
(argv[0]は必ず実行されるプログラムの名前,(OSにより表現が異なる場合がある))

ポインタ変数の配列argv 先頭アドレス:不明

要素番号

0

1

2

値(アドレス)

アドレスa

アドレスb

アドレスc

名前のない文字列 先頭アドレス:アドレスa

要素番号

0

1

2

3

4

5

'g'

'e'

't'

'c'

'l'

'\0'

名前のない文字列 先頭アドレス:アドレスb

要素番号

0

1

2

3

4

5

'h'

'e'

'l'

'l'

'o'

'\0'

名前のない文字列 先頭アドレス:アドレスb

要素番号

0

1

2

3

4

5

'w'

'o'

'r'

'l'

'd'

'\0'


プログラム getcl.c
#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;
    printf("argc=%d\n",argc);
    for (i=0; i<argc; i++) {
        printf("i=%d &argv[%d]=%p argv[%d]=%s\n", i,i,&argv[i],i,argv[i]);
    }
    return 0;
}

3回の実行の様子 (どこのアドレスが使われるかはやってみないとわからない)
>getcl hello world
argc=3
i=0 &argv[0]=04192538 argv[0]=getcl
i=1 &argv[1]=0419253C argv[1]=hello
i=2 &argv[2]=04192540 argv[2]=world

>getcl
argc=1
i=0 &argv[0]=034C84B8 argv[0]=getcl

>getcl abc def ghijk
argc=4
i=0 &argv[0]=03CE2540 argv[0]=getcl
i=1 &argv[1]=03CE2544 argv[1]=abc
i=2 &argv[2]=03CE2548 argv[2]=def
i=3 &argv[3]=03CE254C argv[3]=ghijk



 


4.ポインタへのポインタ(ポインタ変数のアドレスを保存するポインタ変数)




次のようなint型変数があるとする
 int inthensuu
そうすると
 &inthensuu
はそのアドレスなので,
 int *pointa

 pointa=&inthensuu
のようにアドレスを保存できる。ここまでは,既習のことである。

ところで,ポインタ変数pointaも変数なので,&pointaはポインタ変数pointaのアドレスを表している。
そうすると&pointaというアドレスを保存することのできるポインタ変数が考えられる。
このポインタ変数をpntrとすると,次のようなポインタ変数へのポインタ変数が考えられる。

ポインタへのポインタ テストプログラム
#include <stdio.h>

int main(int argc, char *argv[])
{
    int inthensuu=123456;
    int *pointa = &inthensuu;
    int **pntr = &pointa;
    printf("&inthensuu, inthensuu = %p %d\n", &inthensuu, inthensuu);
    printf("&pointa, pointa, *pointa = %p %p %d\n", &pointa, pointa, *pointa);
    printf("&pntr, pntr, *pntr, **pntr= %p %p %p %d\n", &pntr, pntr, *pntr, **pntr);
    return 0;
}

実行結果
&inthensuu, inthensuu = 01D4F848 123456
&pointa, pointa, *pointa = 01D4F850 01D4F848 123456
&pntr, pntr, *pntr, **pntr= 01D4F84C 01D4F850 01D4F848 123456



先の例の
 char *argv[];

 char **argv;
と書くこともできる。
そして *argv は argv[0] を表し,*(argv+1) は argv[1] を表す。


ポインタのポインタは
動的領域確保を伴うリスト構造のプログラム
に出てきます。
namelistというポインタ変数のアドレスをポインタptrptrに保存するので
ptrptrはポインタのポインタとして宣言されています。


動的領域確保を伴うリスト構造のプログラムの一部分
typedef struct list_type {
    char name[32];
    struct list_type *next;
} list_t;

list_t *namelist=NULL;

/*名前の登録*/
void registerName(char *name)
{
    list_t *newptr,**ptrptr=&namelist;
    while (*ptrptr!=NULL) {
        ptrptr=&((*ptrptr)->next);
    }
    newptr=(list_t *)malloc(sizeof(list_t));
    strcpy(newptr->name,name);
    newptr->next=NULL;
    *ptrptr=newptr;
}