9.ポインタ

 Copyright(C) 17Mar2003 coskx

ポインタを使用したプログラムは可読性が低いため,近年ポインタはできるだけ使わないようにする傾向にある。
C言語のポインタは様々な使われ方があるが,ポインタ変数の振る舞いと主な使われ方を以下の順で紹介する。
(1)変数がメモリ上に存在し,コンピュータ内部では変数はアドレスで管理されている。
(2)ポインタ変数にはアドレスを格納できる。
(3)ポインタ変数を使わなければ記述できない配列の動的確保。
(4)関数の型や関数の引数がポインタ変数であることが多いため,これらの関数リファレンスを読んでも困らない程度の知識。
(5)関数で構造体を引数とする場合に用いられる,構造体のアドレス渡し。



この他のトピックとして以下のものがあるが,これは中級向けのものなので省略する。
(1)ポインタ演算
(2)ポインタの配列
(3)関数を指すポインタ
(4)void型のポインタ
(5)2次元配列の動的確保
(6)リストやツリーのデータ構造

 9.1 ポインタ変数の導入

 9.1.1 変数のアドレス

コンピュータ内部での最小記憶単位はbit(ビット)であり,0または1を記憶することができる。

コンピュータ内部でのデータ操作の単位はbyte(バイト)であり,8bitで構成されている。 byteは8桁の 2進法数値を表すことが出来,00000000から11111111までの値を記憶することができる。10進法で表すと0から255までに対応する。16進法で表すと0x0から0xffまでに対応する。デー タはbyte単位で扱われ,その大きさの数え方は,1024byteを1kbyte(キロバイト),1024kbyte=1Mbyte(メガバイ ト),1024Mbyte=1Gbyte(ギガバイト),1024Gbyte=1Tbyte(テラバイト)のようになっている。

コンピュータ内部での変数は,このメモリの一部に保存される。int型変数は連続した4byte,double型変数は連続した8byteが使用されている。
メモリもbyte単位で扱われている。コンピュータのメモリには非常に多くのbyteが並んでいると考えることができる。プログラムからみると,変数はメモリのどこかが使用されているが,どこかを示すためにコンピュータ内部ではアドレスが使われている。
アドレスは0番地から順に1番地,2番地,・・・と言うように数え,各番地には1byteの記憶がなされる。例えば16Gbyteのメモリを載せたコン ピュータでは,アドレスは0番地から,0x3ffffffff番地まである。(通常アドレスは16進法で表す。)
各番地には16進法で2桁(1byteなので)の値が保存されているので次のようなイメージとなる。

 アドレス 
保存されているデータ(状況に応じて変わる)
データは8ビットなので16進法で表示すると2桁になる
0x0
0x45
0x1
0xba
0x2
0x5c
0x3
0xf0


コンピュータが変数の値を操作する時,コンピュータはアドレスを使って変数を特定している。どこのアドレスがその変数に使用されるかはコンパイラシステムやWindowsなどのオペレーティングシステムが決定している。
Cプログラミングではプログラマは変数がどこのアドレスに存在しているのかを考える必要はないが,確認しようと思えば次のように確認することが出来 る。

int myint;
が宣言されている時,
「&myint」の表現は「変数myintが存在しているアドレス」を表す。
(正確には「変数myintが存在している先頭アドレス」というべきである。int型変数は連続4バイトを占めている。)

次の例は「変数myintが存在しているアドレス」を確認するテストプログラムである。
(あくまでも解説用のプログラムであり,実用プログラムでこのようなプログラムを作ることはない。)

List 9.1.1 ある変数がどこのアドレスに存在しているのかを確認するテストプログラム

#include <stdio.h>

int main()
{
    int myint;
    double mydouble;
    myint=12345;
    mydouble=987.654;
    printf("myint   =%10d &myint   =%p\n",myint,&myint);
    printf("mydouble=%10f &mydouble=%p\n",mydouble,&mydouble);
    return 0;
}

/*************** 実行結果 *******************
myint   =     12345 &myint   =0012FF7C
mydouble=987.654000 &mydouble=0012FF74
********************************************/

説明
(1)printf中,アドレスを表すには %p を用いる。
アドレスを表す内部表現がどうなっているのか分からないので,表示の書式として%dや%ldが使えるとは限らない。

(2)実行結果より変数myintはアドレス0012FF7C,変数mydoubleはアドレス0012FF74に割り当てられていることがわかる。
またmydoubleはアドレス0012FF74から0012FF7Bまでの連続8バイト(double型は8バイト) が使用されていることが推測される。
myintはアドレス0012FF7Cから連続4バイト(int型は4バイト)が用いられていると推測される。
アドレスの割り当ては,コンピュータが勝手にやっているので,通常はプログラマは変数のアドレスを知る必要はない。
また,このプログラムを複数回実行するときに,いつも同じアドレスが表示されるとは限らない。

変数名 変数の(先頭)アドレス 変数の値変数の占めているアドレス
myint 0012FF7C 123450012FF7Cから0012FF7F
mydouble 0012FF74 987.6540012FF74から0012FF7B


 重要 変数に&をつけるとその変数のアドレスを得ることができる。 


 9.1.2 アドレスを格納する変数=ポインタ変数

アドレスを格納することが出来る変数はポインタ変数と呼ばれる。
ポインタ変数の宣言では,宣言の時に,「*」を変数名の直前につける。またどのような型の変数のアドレスを格納するつもりなのかを明らかにするために,型をつけて宣言する。
次の例は「int型変数のアドレスを格納するポインタ変数 pi 」と「double型変数のアドレスを格納するポインタ変数 pd 」を宣言し,アドレスを格納し,printfでその変数にアドレスが格納されているところを確認するプログラムである。(あくまでも解説用のプログラムであり,実用プログラムでこのようなプログラムを作ることはない。)

List 9.1.2 ポインタ変数

#include <stdio.h>

int main()
{
    int myint;
    double mydouble;
    int *pi;    /*「pi」はint型変数のアドレスを格納するポインタ変数*/
    double *pd; /*「pd」はdouble型変数のアドレスを格納するポインタ変数*/
    myint=12345;
    mydouble=987.654;
    printf("myint   =%10d &myint   =%p\n",myint,&myint);
    printf("mydouble=%10f &mydouble=%p\n",mydouble,&mydouble);
    pi=&myint;
    pd=&mydouble;
    printf("pi= %p\n",pi); /* %p はアドレスを表示するときの書式である */
    printf("pd= %p\n",pd);
    return 0;
}

/*************** 実行結果 *******************
myint   =     12345 &myint   =0012FF7C
mydouble=987.654000 &mydouble=0012FF74
pi= 0012FF7C
pd= 0012FF74
********************************************/

説明
ポインタ変数pi,pdには,変数myint,mydoubleのアドレスが格納されているため,
printf(%p使用)でそれらのポインタ変数を表示すると,変数myint,mydoubleのアドレスが表示される。
pi,pdも変数なので,これらの変数はどこかのアドレスに存在する。
しかしこのプログラムでは
pi,pdがどこのアドレスに存在するかは解らない。

 変数名    変数の(先頭)アドレス    変数の値 
myint 0012FF7C 12345
mydouble 0012FF74 987.654
pi 不明 (&piで得ることができる) 0012FF7C
pd 不明 (&pdで得ることができる 0012FF74


「int *pi;」が宣言されるとpiはポインタ変数となり,piにはint型変数のアドレスを格納することができる。
「double *pd;」が宣言されるとpdはポインタ変数となり,pdにはdouble型変数のアドレスを格納することができる。

 重要 ポインタ変数にはアドレスを保存することができる。 

「int *pi;」が宣言されると「pi」は「int型変数を指すポインタ変数」あるいは単に「intを指すポインタ」と呼ばれる。
「double *pd;」が宣言されると「pd」は
double型変数を指すポインタ変数」あるいは単に「doubleを指すポインタ」と呼ばれる。

注意 「ポインタ」の言葉を「ポインタ変数」の意味に用いる場合と,「指しているアドレス」の意味に用いる場合がある。
本解説では紛らわしい場合には「ポインタ変数」の場合は「ポインタ」とは呼ばないで,必ず「ポインタ変数」と呼ぶこととする。


 9.1.3 ポインタ変数のテスト

プログラムの実行部で,ポインタ変数の直前に「*」をつけると,ポインタ変数が保持しているアドレスにある変数を表す。
次の例は,ポインタ変数の直前に「*」をつけ,ポインタ変数が保持しているアドレスにある変数の値を参照したり,変更するところを確認するプログラムである。(あくまでも解説用のプログラムであり,実用プログラムでこのようなプログラムを作ることはない。)

List 9.1.3 ポインタ変数のテスト

#include <stdio.h>

int main()
{
    int myint;
    double mydouble;
    int *pi;    /*int型変数のアドレスを格納するポインタ変数*/
    double *pd; /*double型変数のアドレスを格納するポインタ変数*/
    myint=12345;
    mydouble=987.654;
    printf("myint   =%10d &myint   =%p\n",myint,&myint);
    printf("mydouble=%10f &mydouble=%p\n",mydouble,&mydouble);
    pi=&myint;
    pd=&mydouble;
    printf("pi= %p\n",pi);
    printf("pd= %p\n",pd);
    /*ここまでは前のプログラムと同じ*/

    printf("*pi= %d\n",*pi); /*ポインタ変数piに格納されたアドレスにあるint型変数の値*/
    printf("*pd= %f\n",*pd);/*ポインタ変数pdに格納されたアドレスにあるdouble型変数の値*/

    *pi=100;  /*ポインタ変数piに格納されたアドレスにあるint型変数の値を100に変更*/
    *pd=100.0;/*ポインタ変数pdに格納されたアドレスにあるdouble型変数の値を100.0に変更*/
    printf("myint   =%10d\n",myint);
    printf("mydouble=%10f\n",mydouble);
    return 0;
}

/*************** 実行結果 *******************
myint   =     12345 &myint   =0012FF7C
mydouble=987.654000 &mydouble=0012FF74
pi= 0012FF7C
pd= 0012FF74
*pi= 12345
*pd= 987.654000
myint   =       100
mydouble=100.000000
********************************************/

説明
ポインタ変数pi,pdには,変数myint,mydoubleのアドレスが格納されているため,
*piでmyintの値,*pdでmydoubleの値が参照でき,また値の変更も出来る。
すなわち,プログラム中で*piの表現とmyintの表現は全く同じものとなる。
また*pdの表現とmydoubleの表現は全く同じものとなる。

 変数名   変数のアドレス   変数の値 
myint 0012FF7C 12345
mydouble 0012FF74 987.654
pi 不明(printfで&piを表示すれば判明する) 0012FF7C
pd 不明(printfで&pdを表示すれば判明する) 0012FF74


 重要 ポインタ変数に*をつけると,ポインタ変数に保存されているアドレスにある変数を操作できる。 


まとめ int型変数「myintvar」に値1234が保存されていて,この変数がメモリ内で0x0015ff90に存在すると

 変数名   変数のアドレス   変数の値 
myintvar 0x0015ff90 1234

&myintvar は 0x0015ff90 であり
int *mypointer;
mypointer=&myintvar;
ならば,*mypointer は1234を表している。

わかりにくいが
int *mypointer=&myintvar;
このように変数宣言時に初期化しても同じ意味になる。


 9.1.4 ポインタ変数の利用でよくある間違い

通常の変数は,宣言すれば使用できる。しかしポインタ変数は,意味のあるアドレスを代入してから 利用しないと問題が起こる。ポインタ変数にはプログラム起動時に何かの値(なにが入っているかは不定)が入っており,その値はアドレスとして解釈されるた め,ポインタ変数の指す変数を参照しよう(取り出そう)とすると意味不明の値が取り出されたり,プログラムが暴走することがある。

List 9.1.4 ポインタ変数に意味のあるアドレスを代入せずに,ポインタ変数の指す変数を参照した

#include <stdio.h>

int main()
{
    int *p;
    printf("*p=%d\n",*p);
    return 0;
}

コンパイル時に警告が出ました。
xxxx.c(6) : warning C4700:
 値が割り当てられていないローカルな変数 'p' に対して参照が行われました。
むりやり,実行したら,実行時エラーで停止しました。

List 9.1.5 ポインタ変数に意味のあるアドレスを代入せずに,ポインタ変数の指す変数に値を代入した

#include <stdio.h>

int main()
{
    int *p;
    *p=100;
    printf("*p=%d\n",*p);
    return 0;
}

コンパイル時に警告が出ました。
xxxx.c(6) : warning C4700:
 値が割り当てられていないローカルな変数 'p' に対して参照が行われました。
むりやり,実行したら,実行時エラーで停止しました。
ポインタ変数pにはゴミの値が入っており,その値をアドレスだとして,

*p=100;
で,そのゴミアドレスに書き込もうとしたので実行時エラーがおこる。

List 9.1.6 ポインタ変数にint型変数のアドレスを代入してから,ポインタ変数の指す変数に値を代入した
(これは問題ない)

#include <stdio.h>

int main()
{
    int x;
    int *p;
    p=&x;
    *p=100;
    printf("*p=%d\n",*p);
    return 0;
}

実行結果

*p=100

このプログラムではpにint型変数xのアドレスが入っているため,そのアドレスに値100が書き込まれ,
その作業はint型変数xに値100を書き込んだのと同じことになる。


コラム scanfの 「&」

scanfではキーボードから読み込んだ値を変数に格納してほしい時,変数名に「&」をつけた。
実は関数scanfに変数のアドレスを渡して,関数
scanf側で,そのアドレス位置に,読み込んだ値を格納していたのである。
関数scanfは格納すべき変数のアドレスだけもらっているので,呼び出し側でどのような名前の変数だったのかは知ることは出来ない。

int myvar;
   :
scanf("%d",&myvar); /*関数scanfにmyvarのアドレスを渡して,そのアドレス位置に,読み込んだ値を格納してもらう*/
   :

もし,scanfを呼び出すときに「&」を付け忘れたらどうなるだろう。

int myvar;
   :
scanf("%d",myvar); /*関数scanfにmyvar内の値(この場合はゴミの値)を渡している*/
   :

関数scanfはもらったゴミの値をアドレスとして受取り,キーボード入力された値を,そのアドレスに書き込む。
もともとゴミアドレスだから,メモリ内のとんでもないところに値を書き込むため,実行時エラーとなる。

 

 9.2 ポインタ変数とライブラリ関数

 9.2.1 ポインタ変数と関数puts()

関数puts()は次のようなプロトタイプ宣言となっている。

int puts( const char *string )
stdout ストリーム(ここではコンソール画面)に文字列stringを書き込み,改行します。
返す値:処理が正常に終了した場合負でない値を返します。失敗した場合、puts 関数は EOF を返します。
引数:string 出力すべき文字列の格納場所の先頭アドレスを受け取る

charを指すポインタ変数が引数となっている。
なお「const」は「この関数内で文字列stringは変更されない」の意味である。

これまで説明してこなかったが,puts()に引数で文字列全体を渡しているのではなく,引数として文字列の先頭要素のアドレス(「文字列の先頭アドレス」と もいう)を渡していただけである。ただし,文字列の終端には'\0'が入っていることが保障されているため,関数putsは先頭アドレスの提供だけで困ることはない。。

puts(mystring) では,「mystring(例えば "Don't Touch Me." )という文字列を渡している」と説明してきたが,
実はmystringという配列の先頭アドレスを渡しただけである。

ここでC言語で重要なアドレスに関する文法を紹介する。

  「配列の名前」は,「配列の先頭要素のアドレス」を意味する。

char mystring[128]="Don't Touch Me.";
のもとで,
put(mystring);
というのは,char型の配列mystringの先頭要素のアドレス(「char型の配列mystringの先頭アドレス」ともいう)を渡していることになり,
関数put(const char *string)に配列mystringの文字列を画面表示してもらうことを意味する。
関数put(const char *string)から見るとstringに受け取ったアドレスから始まる文字列は'\0'が終端に入っているので,文字列の先頭から'\0'が格納されている直前までを表示するようになっている。


次の例は関数puts()に文字列buff先頭アドレスや途中のアドレスを渡している例である。
&mystring[0]」は「配列mystringの第0要素 のアドレス」の意味である。
 (「mystring」と書いた場合でも,mystringの先頭アドレスを意味している。)
&mystring[6]」は「配列mystringの第6要素 のアドレス」の意味である。(すなわち文字Tのアドレス)

  01234567890123456789
  Don't Touch Me.

List 9.2.1 関数puts()

#include <stdio.h>

int main()
{
    char mystring[128]="Don't Touch Me.";
    puts(mystring);
    puts(&mystring[0]);
    puts(&mystring[6]);
    return 0;
}

/*************** 実行結果 *******************
Don't Touch Me.
Don't Touch Me.
Touch Me.
********************************************/

関数putsはC言語の文字列の決まりを使って動作している えらいっ!
関数putsは文字列の先頭アドレスだけを受け取っている。
先頭アドレスだけ受け取っても,文字列がどこで終わるのかわからなくかったら困ってしまう。
C言語では,文字列の終わりには終端文字\0を置くことが決まりである。
関数putsはこの決まりに従って,先頭文字から終端文字\0の前までを表示している。

 

 9.2.2 ポインタ変数と関数gets()

文字列を操作する関数の説明には,ポインタ変数がよく出てくる。例えば関数gets()は次のようなプロトタイプ宣言となっている。

char *gets( char *buffer )
stdin ストリームから 1 行読み込みます。
返す値:成功すると引数の値(読み込み用char配列の先頭番地)を返します。エラーが発生するか、
    ファイルの終端を検出すると NULL ポインタを返します。
引数:buffer 入力文字列の格納場所の先頭アドレス

関数名の直前に「*」が付くと,ポインタ(アドレス)を返す関数(この場合はcharを指すアドレス(ポインタ)を返す関数)
の意味になる。すなわち
char *gets( char *buffer )
は,charを指すポインタ変数が引数となっており,charを指すアドレスを返す関数getsと読む。

ここでC言語で重要なアドレスに関する文法を再度紹介する。

  「配列の名前」は,「配列の先頭要素のアドレス」を意味する。
char buff[128];
のもとで,
gets(buff);
というのは,char型の配列buffの先頭要素のアドレス(「char型の配列buffの先頭アドレス」ともいう)を渡していることになり,キーボード入力で得られた文字列をbuffに受け取ることになる。buffの配列サイズは '\0'が入るところも含めて,入力文字数以上でなければならない。
buffに受け取った文字列は'\0'が終端に入っていることが保証されている。


なお関数gets()から返された値は普通見ないが,見てもよい。次の例は関数getsの動作を 示す例である。

List 9.2.2 関数gets()

#include <stdio.h>

int main()
{
    char buff[128];
    char *ret;
    printf("buff:%p\n",buff);
    do {
        ret=gets(buff); /*普通このretは見ることはない*/
        printf("ret=%p buff=[%s]\n",ret,buff);
    } while (ret!=NULL);
    return 0;
}

/*************** 実行結果 *******************
buff:0012FF00
Hello.
ret=0012FF00 buff=[Hello.]
This is gets test.
ret=0012FF00 buff=[This is gets test.]
again
ret=0012FF00 buff=[again]
^Z
ret=00000000 buff=[again]
********************************************/

説明
正常に入力されると,retには0012FF00が入り,これはbuffの先頭要素アドレスである。
「^Z」(コントロールキーを押しながらZを入力)は 終了の合図。
retにはNULL(00000000)が入り,buffは変更されていない。

なお「^Z」は WindowsPCの場合であり,UNIXでは「^D」である。

 

 9.2.3 ポインタ変数と関数strcpy()

関数strcpy()は次のようなプロトタイプ宣言となっている。

char *strcpy( char *string1, const char *string2 )
文字列をコピーします。
返す値:コピー先の文字列を返します。返すべきエラー値はありません。
引数:string1 コピー先の文字列の先頭アドレス
   string2 コピー元の文字列の先頭アドレス

charを指すポインタ変数が引数となっている。コピー元の文字列では文字列の終端にには '\0'が入っていることを保証しなければならない。コピー先の文字型配列のサイズは'\0'が入るところも含めて,コピー結果が入る大きさでなければな らない。
次の例は関数strcpyの使用例である。

List 9.2.3 関数strcpy()

#include <stdio.h>
#include <string.h>

int main()
{
    char original[128]="Don't Touch Me.";
    char copied[128];
    puts(original);
    strcpy(copied,original);
    puts(copied);
    strcpy(copied,&original[6]); /*「&original[6]」はoriginalの第6要素のアドレスの意味*/
    puts(copied);
    return 0;
}

/*************** 実行結果 *******************
Don't Touch Me.
Don't Touch Me.
Touch Me.
********************************************/

コラム scanfと文字列読み込み

scanfではキーボードから読み込んだ文字列をchar型配列に格納してほしい時,配列名のみ記述した。
例 scanf("%d",&value);  /*valueという変数に整数を読み込む*/
実は関数scanfに配列の先頭アドレスを渡して,関数側で,そのアドレス位置に,読み込んだ文字列を格納してもらう。

配列名は配列の先頭アドレスを表す決まりなので,配列名に「&」をつける必要はない

char mystring[128];
   :
scanf("%s",mystring); /*関数scanfにmystringのアドレスを渡して,そのアドレス位置に,読み込んだ値を格納してもらう。配列名は先頭アドレスを表しているので 「&」は不要*/
   :

 

 9.3 ポインタ変数による文字列操作関数

 9.3.1  ポインタ変数の振る舞いテスト

文字列は,メモリ上に文字コードが連続して並んでおり,終端は\0になっている。ポインタ変数の振る舞いを見るためのテストプログラムで,確認しよう。

text という表現は,文字列"abc"の先頭アドレス意味している。
実行結果をみると文字列"abc"はアドレス0012FF6Cに存在している。
このことからメモリの状況は次のようになっていると考えられる。

アドレス 内容
0012FF6C

a

0012FF6D

b

0012FF6E

c

0012FF6F

\0

「ptr++」でptrを1増加すると,ptrは0012FF6Cから0012FF6D,0012FF6Eのように増えていく。
「(*ptr)++」だとptrの指しているアドレスにある変数の値が増えてしまうことになるので,
例えばptrが0012FF6Eだったら,このアドレスにある文字コードデータ「c」が「d」になる。

List 9.3.1 charを指すポインタ変数のテスト

#include "stdio.h"

int main()
{
    char text[]="abc";
    char *ptr;
    printf("text=%p %s\n",text,text);
    ptr=text;
    printf("ptr=%p %s\n",ptr,ptr);
    printf("ptr=%p %c %x\n",ptr,*ptr,*ptr);
    ptr++;
    printf("ptr=%p %c %x\n",ptr,*ptr,*ptr);
    ptr++;
    printf("ptr=%p %c %x\n",ptr,*ptr,*ptr);
    (*ptr)++;
    printf("ptr=%p %c %x\n",ptr,*ptr,*ptr);
    printf("text=%p %s\n",text,text);
    return 0;
}

/**************実行結 果**********************
text=0012FF74 abc
ptr=0012FF74 abc
ptr=0012FF74 a 61
ptr=0012FF75 b 62
ptr=0012FF76 c 63
ptr=0012FF76 d 64
text=0012FF74 abd
********************************************/

 

 9.3.2  ポインタ変数を利用した文字列コピー関数

文字列操作を行う関数には、ポインタ変数(char *)がよく使われる。例えば,文字列コピー関数を作ってみると次のようになる。
d++やs++では,これらのポインタ変数に格納されているアドレス値が1つ増加する。

List 9.3.2 文字列コピー関数

#include <stdio.h>

/*文字列コピー関数 配列版*/
void mystrcpy1(char d[],char s[])
{
    int i=0;
    while ( s[i] != '\0' ) {
        d[i]=s[i];
        i++;
    }
    d[i]='\0';
}

/*文字列コピー関数 ポインタ版*/
void mystrcpy2(char *d,char *s)
{
    while ( *s != '\0' ) {
        *d=*s;
        d++;
        s++;
    }
    *d='\0';
}

int main()
{
    char str1[]="Hello world";
    char str2[32];
    char str3[32];
    mystrcpy1(str2,str1);
    mystrcpy2(str3,str1);
    puts(str1);
    puts(str2);
    puts(str3);
    return 0;
}

実行結果
Hello world
Hello world
Hello world

文字列コピー関数に関する補足

ここではポインタ変数に++演算を行なうと,ポインタ変数内のアドレス値は1増加したが,いつもアドレス値が1だけ増えるわけではない。int型を指すポインタ変数ではアドレス値が4ずつ増加する。

ポインタの使いかたは奥が深い。中級になってから学ぶこと。

中級向け補足


 9.3.3 ポインタ変数と関数strstr(),strchr()

関数strstr()は次のようなプロトタイプ宣言となっている。

char *strstr( const char *string1, const char *string2 )

第1文字列中に含まれる第2文字列を見つけます。
返す値:string2 が string1 内に最初に現れた位置へのポインタを返します。string2 がstring1 内に見つからない場合は NULL ポインタを返します。string2 が長さ 0 の文字列を指している場合は、string1 を返します。
引数:string1 検索される文字列
   string2 検索する文字列

関数strchr()は次のようなプロトタイプ宣言となっている。

char *strchr( const char *string, int c )

文字列中に含まれる特定の文字を見つけます。
返す値:string 中で c が最初に現れた位置へのポインタを返します。文字が見つからないと NULL ポインタを返します。
引数:string 検索される文字列
   c 検索する文字

次の例は,文字列「I like travelling and reading.」中に含まれる文字列「ing」の個数を求め,次に文字「a」を「#」に置き換える操作を行なうプログラムである。

List 9.3.7 関数strstr()とstrchr()

#include <stdio.h>
#include <string.h>

int main()
{
    char sentence[128]="I like travelling and reading.";
    char *ptr;
    int counter;
    printf("sentence=%p %s\n",sentence,sentence);

    /*"ing"の数を数える*/
    counter=0;
    ptr=sentence;
    ptr=strstr(ptr,"ing");
    while (ptr!=NULL) {
        counter++;
        printf("ptr=%p %s\n",ptr,ptr);/*スナップショット*/
        ptr++;/*1増やしてから探さないと同じ物を探し出してしまう*/
        ptr=strstr(ptr,"ing");
    }
    printf("ptr=%p counter=%d\n",ptr,counter);

    /*sentence中の'a'を'#'に変更する*/
    ptr=sentence;
    ptr=strchr(ptr,'a');
    while (ptr!=NULL) {
        printf("ptr=%p %s\n",ptr,ptr);/*スナップショット*/
        *ptr='#';
        printf("ptr=%p %s\n",ptr,ptr);/*スナップショット*/
        ptr++;/*1増やしてから探さないと同じ物を探し出してしまう*/
        ptr=strchr(ptr,'a');
    }
    printf("sentence=%p %s\n",sentence,sentence);
    return 0;
}

/*************** 実行結果 *******************
sentence=0012FF00 I like travelling and reading.
ptr=0012FF0E ing and reading.
ptr=0012FF1A ing.
ptr=00000000 counter=2
ptr=0012FF09 avelling and reading.
ptr=0012FF09 #velling and reading.
ptr=0012FF12 and reading.
ptr=0012FF12 #nd reading.
ptr=0012FF18 ading.
ptr=0012FF18 #ding.
sentence=0012FF00 I like tr#velling #nd re#ding.
********************************************/


NULLに関する補足 (NULLはナルと読む。ヌルは日本語ローマ字読み(ドイツ語読み)である)
 NULLはポインタ変数に代入できる値(ポインタ定数)で,どこも指していないことを意味している。
 NULLをint型変数やchar型変数に代入するのは意味として誤りである。
(コンパイルエラーにはならないが,警告が表示される。)
 NULLは0であると思われているが,これはヘッダーファイル内で定義されているだけで,0である保障はない。


 課題 9 その1

(1)文字列の先頭アドレスを受け取り,その文字列の長さを返す関数
int mystrlen(char *str)
を作成し,テスト用mainも作りなさい。    (p09ex01.c)
ただし,関数内でポインタ変数strに関してstr[i]のような文字型配列表現を用いてはならない。
次の検証用mainを使いなさい。
int main()
{
    char  str1[]="exercises";
    char  str2[]="pointer programing";
    int cnt1,cnt2;
    cnt1=mystrlen(str1);
    cnt2=mystrlen(str2);
    printf("%s %d\n",str1,cnt1);
    printf("%s %d\n",str2,cnt2);
    return 0;
}

(2)文字列sの左からn文字を文字列dにコピーする関数
void left(char *d,char *s,int n)
を作成しなさい。ただし関数left内でstrで始まるライブラリ関数およびprintfを用いてはならない。
もし文字列sの長さが足りなかったらあるだけコピーするように作りなさい。    (p09ex02.c)
ただし,関数内でポインタ変数d,sに関してd[i],s[i]のような文字型配列表現を用いてはならない。

例えば
char str1[]="exercises";
char str2[32]="abcdefghijklmn"; /*検証のためあらかじめ何らかの文字列を入れておく*/
の時
left(str2,str1,5); → str2には"exerc"がコピーされる。
left(str2,str1,10); → str2には"exercises"コピーされる。
一般的に関数では仕様にない機能はつけてはいけない。
(例えば関数内で画面表示してはならない。デバッグのために関数内で画面表示した場合は,提出時には消しておく。)
次の検証用mainを使いなさい。

int main()
{
    char str1[]="exercises";
    char str2[32]="abcdefghijklmn";
    char str3[32]="abcdefghijklmn";
    left(str2,str1,5);
    printf("--!%s!--\n",str2);   /*--!exerc!--が表示されるはず*/
    left(str3,str1,10);
    printf("--!%s!--\n",str3);   /*--!exercises!--が表示されるはず*/
    return 0;
}


(3) 文字列sの右からn文字を文字列dにコピーする関数
void right(char *d,char *s,int n)
を作成しなさい。ただし関数right内でstrで始まるライブラリ関数およびprintfを用いてはならない。
もし文字列sの長さが足りなかったらあるだけコピーするように作る。  (p09ex03.c)
ただし,関数内でポインタ変数d,sに関してd[i],s[i]のような文字型配列表現を用いてはならない。

例えば
char str1[]="exercises";
char str2[32]="abcdefghijklmn"; /*検証のためあらかじめ何らかの文字列を入れておく*/
の時
right(str2,str1,5); → str2には"cises"がコピーされる。
right(str2,str1,10); → str2には"exercises"がコピーされる。
次の検証用mainを使いなさい。

int main()
{
    char str1[]="exercises";
    char str2[32]="abcdefghijklmn";
    char str3[32]="abcdefghijklmn";
    char str4[]="Tokyo";
    char str5[32]="abcdefghijklmn";
    char str6[32]="abcdefghijklmn";
    right(str2,str1,5);
    printf("--!%s!--\n",str2);   /*--!cises!--が表示されるはず*/
    right(str3,str1,10);
    printf("--!%s!--\n",str3);   /*--!exercises!--が表示されるはず*/
    right(str5,str4,3);
    printf("--!%s!--\n",str5);   /*--!kyo!--が表示されるはず*/
    right(str6,str4,10);
    printf("--!%s!--\n",str6);   /*--!Tokyo!--が表示されるはず*/
    return 0;
}

(4
)文字列sの左からm文字目か らn文字を文字列dにコピーする関数
void mid(char *d,char *s,int m,int n)
を作成しなさい。
m文字目というのは,1から数えることとする。(例えばabcdefgの4文字目はdである)
ただし関数mid内でstrで始まるライブラリ関数およびprintfを用いてはならない。
もし文字列sの長さが足りなかったらあるだけコピーするように作りなさい。
また,もし文字列sの文字数がm未満だったら,コピーすべき文字列がないので,
dには空の文字列を設置しなさい。(空の文字列=文字列の先頭が'\0')

ただし,関数内でポインタ変数d,sに関してd[i],s[i]のような文字型配列表現を用いてはならない。
(p09ex04.c)


例えば
char str1[]="exercises";
char str2[32]="abcdefghijklmn"; /*検証のためあらかじめ何らかの文字列を入れておく*/
の時
mid(str2,str1,2,5); → str2には"xerci"がコピーされる。
mid(str2,str1,2,10); → str2には"xercises"がコピーされる。
mid(str2,str1,12,3); → str2は空の文字列になる。
次の検証用mainを使いなさい。

int main()
{
    char str1[]="exercises";
    char str2[32]="abcdefghijklmn";
    char str3[32]="abcdefghijklmn";
    char str4[32]="abcdefghijklmn";
    char str5[]="Tokyo";
    char str6[32]="abcdefghijklmn";
    char str7[32]="abcdefghijklmn";
    puts(str1);
    mid(str2,str1,2,5);
    printf("--!%s!--\n",str2);   /*--!xerci!--が表示されるはず*/
    mid(str3,str1,2,10);
    printf("--!%s!--\n",str3);   /*--!xercises!--が表示されるはず*/
    mid(str4,str1,12,3);
    printf("--!%s!--\n",str4);   /*--!!--が表示されるはず*/
    mid(str6,str5,2,1);
    printf("--!%s!--\n",str6);   /*--!o!--が表示されるはず*/
    mid(str7,str5,2,10);
    printf("--!%s!--\n",str7);   /*--!okyo!--が表示されるはず*/
    return 0;
}

(5)文字列sを逆順に並べ替える関数
void reverseString(char *s)
を作成しなさい。ただし関数reverseString内でstrで始まるライブラリ関数およびprintfを用いてはならない。
  (p09ex05.c)
ただし,関数内でポインタ変数sに関してs[i]のような文字型配列表現を用いてはならない。

例えば
char str[]="exercises";
の時
reverseString(str); → strは"sesicrexe"に変更される。

次の検証用mainを使いなさい。

int main()
{
    char str[30]="exercises";
    char str2[30]="java";
    printf("--!%s!--\n",str);
    reverseString(str);
    printf("--!%s!--\n",str);
    printf("--!%s!--\n",str2);
    reverseString(str2);
    printf("--!%s!--\n",str2);
    return 0;
}


 

 9.4 ポインタ変数による配列の動的確保


これまで,配列は次のように宣言され,変数宣言の時点でその大きさが決まっていた。

int garray[1000];

int main()
{
    int array[100];
       :

このような配列は静的配列と呼ばれる。
ところで,プログラムが起動後に配列のサイズがを決めたいがある。例えば,

int main()
{
    int size;
     :
    scanf("%d",&size);
    int array[size];
     :

のようにしたいことがある。このように,プログラムが起動してから配列の大きさを決めることができれば,
その配列は動的配列となる。
しかし,変数は実行文より前に宣言されなかればならないため,このようなプログラムはコンパイルエラーになってしまう。

それでは,ユーザ関数内での配列を作ったらどうだろう。

int myfunc(int size)
{
    int array[size];
     :

このようなプログラムなら,実行文より前に配列変数の宣言ができる。
しかし残念ながら,これもコンパイルエラーになってしまう。
配列サイズを書くときは定数である必要があり,変数(上記例ではsize)を用いることは出来ない。

そこで,配列の大きさをプログラム起動後に決める方法は,配列の動的確保と呼ばれる方法である。
この方法ではポインタを使う必要がある。

次のような配列はポインタによる動的確保をするのが好ましい。
(1)1Mバイトを超えるような大きな配列
(2)プログラミングの時点で大きさが確定せず,プログラムが起動してから大きさが確定する配列

配列の動的管理は次の手順による。
配列を作る予定の型のポインタ変数を用意する
関数malloc(),あるいは関数calloc()により必要な配列のためのメモリを確保し,返された値をポインタ変数に格納する
ポインタ変数を配列と同じように使用する
作業が終わったら関数free()でメモリを解放する

List 9.4.1 大きな配列の動的確保

#include <stdio.h>
#include <stdlib.h>

#define SIZE 1000000

int main()
{
    int *array;/*この変数が後で配列として扱えるようになる*/
    int i;
    int error=0; /*エラーがあったら1,そうでなかったら0*/
    array=(int *)malloc(SIZE*sizeof(int));
    if (array==NULL) {
        printf("** out of memory **\n");
        exit(1);
    }

    /*これ以降arrayは配列として使える*/
    printf("allocated array=%p\n",array);
    for (i=0;i<SIZE;i++) array[i]=i; /*テストのための代入*/

    /*配列が機能しているかどうかの検査*/
    for (i=0;i<SIZE;i++) {
        if (array[i]!=i) {
            printf("** memory error **\n"); /*これが表示されたら異常事態*/
            error=1;
            break;
        }
    }
    if (error==0) printf("no error\n");
    free(array);
    return 0;
}

/*************** 実行結果 *******************
allocated array=00540040
no error
********************************************/

追加説明

(1)関数malloc
メモリを確保する関数である。
確保しなければメモリを自分用に使うことができない。

関数の仕様
void *malloc(size_t size)
引数sizeは単位バイトで与える。何バイトメモリを確保してほしいかを伝える。
関数の返す値は,確保できたメモリ(連続したsizeバイトのメモリ)の先頭アドレスである。
メモリの確保に失敗するとNULLを返す。
確保されたメモリは初期化されていないため,どのような値が入っているかは定まっていない。
読み方 マロック と読むのが多数のようだ。
語源は memory allocation (allocationは割当の意味)と言われている。

参考 関数calloc
void *calloc(size_t nelements, size_t bytes)
引数では,bytesバイトで構成された変数を,nelements個,確保したいと伝える。
関数の返す値は,確保できたメモリ(連続したsizeバイトのメモリ)の先頭アドレスである。
メモリの確保に失敗するとNULLを返す。
確保されたメモリは0で初期化されている。
読み方 カロック,キャロック(USA) と読むのが多数のようだ。
語源は contiguous allocation あるいは clear allocate と言われている。

(2)sizeof演算子
sizeof(int)はint型の変数は何バイトであるかを示している。値は4である。
各変数は何バイトで構成されているかをテストするプロ グラム

#include <stdio.h>

int main()
{
    printf("sizeof(int)      =%d [byte(s)]\n",sizeof(int));
    printf("sizeof(short int)=%d [byte(s)]\n",sizeof(short int));
    printf("sizeof(double)   =%d [byte(s)]\n",sizeof(double));
    printf("sizeof(char)     =%d [byte(s)]\n",sizeof(char));
    return 0;
}

/*実行結果
sizeof(int)      =4 [byte(s)]
sizeof(short int)=2 [byte(s)]
sizeof(double)   =8 [byte(s)]
sizeof(char)     =1 [byte(s)]
*/

List 9.4.2 プログラム起動後に大きさの決まる配列の動的確保

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *array;/*この変数が後で配列として扱えるようになる*/
    int size;
    int i;
    int error=0; /*エラーがあったら1,そうでなかったら0*/
    printf("必要な配列のサイズ:");
    scanf("%d",&size);
    array=(int *)malloc(size*sizeof(int));
    if (array==NULL) {
        printf("** out of memory **\n");
        exit(1);
    }
    /*これ以降arrayは配列として使える*/
    printf("allocated array=%p\n",array);
    for (i=0;i<size;i++) array[i]=i; /*テストのための代入*/
    for (i=0;i<size;i++) { /*検査*/
        if (array[i]!=i) {
            printf("** memory error **\n");
            error=1;
            break;
        }
    }
    if (error==0) printf("no error\n");
    free(array);
    return 0;
}

/*************** 実行結果 *******************
必要な配列のサイズ:1000
allocated array=003461F8
no error
********************************************/

配列確保と配列開放を関数化すると次のようになる。関数を再利用することにすると,呼び出し側 を作る時に楽が出来る。

List 9.4.3 int型配列動的確保の関数化

#include <stdio.h>
#include <stdlib.h>

/*int型配列確保関数 要素数はsize*/
/*返す値は確保された配列の先頭番地*/
int *openIntArray(int size)
{
    int *array=(int *)malloc(size*sizeof(int));
    if (array==NULL) {
        printf("** out of memory **\n");
        exit(1);
    }
    return array;
}

/*int型配列解放関数*/
void closeIntArray(int *array)
{
    free(array);
}

int main()
{
    int *array;/*この変数が後で配列として扱えるようになる*/
    int size;
    int i;
    int error=0; /*エラーがあったら1,そうでなかったら0*/
    printf("必要な配列のサイズ:");
    scanf("%d",&size);
    array=openIntArray(size);
    /*これ以降arrayは配列として使える*/
    printf("allocated array=%p\n",array);
    for (i=0;i<size;i++) array[i]=i; /*テストのための代入*/
    for (i=0;i<size;i++) { /*検査*/
        if (array[i]!=i) {
            printf("** memory error **\n");
            error=1;
            break;
        }
    }
    if (error==0) printf("no error\n");
    closeIntArray(array);
    return 0;
}

/*************** 実行結果 *******************
必要な配列のサイズ:1000
allocated array=003461F8
no error
********************************************/

前の例はint型の配列の確保の関数だが,拡張して,どのような型の配列にでも対応できるようにする。

List 9.4.4 配列動的確保の関数化

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int x;
    int y;
} point_t;

/*配列確保関数 要素数はsize 1要素当たりのバイト数はsizeofvariable*/
/*返す値は確保された配列の先頭番地*/
void *openArray(int size, int sizeofvariable)
{
    void *array=malloc(size*sizeofvariable);
    if (array==NULL) {
        printf("** out of memory **\n");
        exit(1);
    }
    return array;
}

/*配列解放関数*/
void closeArray(void *array)
{
    free(array);
}

int main()
{
    int *array1;/*この変数が後でint型配列として扱えるようになる*/
    point_t *array2;/*この変数が後でpoint_t型配列として扱えるようになる*/
    int size=5;
    int i;
    point_t tmp;
    array1=openArray(size,sizeof(int));
    array2=openArray(size,sizeof(point_t));
    /*これ以降array1,array2は配列として使える*/
    printf("allocated array1 at %p\n",array1);
    printf("allocated array2 at %p\n",array2);
    for (i=0;i<size;i++) { /*テストのための代入*/
        array1[i]=i;
        tmp.x=10+i,tmp.y=40+i;
        array2[i]=tmp;
    }
    for (i=0;i<size;i++) { /*検査*/
        printf("array1[%d]=%d array2[%d]=(%d,%d)\n",i,array1[i],i,array2[i].x,array2[i].y);
    }
    closeArray(array1);
    closeArray(array2);
    return 0;
}

/*************** 実行結果 *******************
allocated array1 at 00430120
allocated array2 at 004300C0
array1[0]=0 array2[0]=(10,40)
array1[1]=1 array2[1]=(11,41)
array1[2]=2 array2[2]=(12,42)
array1[3]=3 array2[3]=(13,43)
array1[4]=4 array2[4]=(14,44)
********************************************/

どうしてポインタ変数が配列変数に変わってしまうかというと,次の理由による。

要点

int *ptr;
の宣言のもとで,
「*ptr」と「ptr[0]」は同じ意味である。
同様に「*(ptr+5)」と「ptr[5]」も同じ意味である。

逆に int array[10]
の宣言のもとで
「array[0]」と「*array」は同じ意味である。
同様に「array[5]」と「*(array+5)」も同じ意味である。
arrayは値の変更が許されない,定数ポインタと考えることができる。


補足 mallocで確保できる大きさ

mallocではどのくらい大きな領域(バイト数で数える)を取ることができるのかというと,それは使っているシステムに依存する。
現在使っているコンパイラでは,整数を表わすのは32bitであり,その最大値429496729である。
malloc(size*sizeofvariable);このように使うときには,size*sizeofvariableの値が整数で表わせる値を超えないようにする必要がある。
実は,mallocの引数はsize_t型となっていて,size_t型のビット数はコンパイラに依存している。
(現在使用しているコンパイラではsize_tは32bit)

mallocが返すポインタ変数にしても,現在使っているコンパイラでは,32ビットであるが,64ビット,128ビットを使うシステムもある。
また実際,実装されているメモリの大きさに依存することも考えられるが,不足分は外部記憶(ハードディスクなど)上の仮想メモ
リを使うシステムもあり,使えるメモリの大きさはOSに依存する。
小さなマイコンでは,実装されているメモリの大きさに依存するため,注意が必要である。

補足 realloc

配列を動的に確保したが,作業中に足りなくなって,もっと大きな配列に変更したくなることがある。
(多くの場合,最初の見通しが甘かったことになるが)
そのような場合はrealloc関数を用いると,配列の取り直しと,中身のコピーが行われ,最初から大きな
配列が確保されているように使える。
reallocの引数は2つあり,第1引数は古い領域の先頭アドレス,第2引数は新たに確保要求するバイト数である。

関数が正常に動作した場合,使い終えた領域(第1引数)はreallocが開放しているため,
realloc関数終了後に開放する必要はない。
realloc終了後にfree操作をすると二重解放となり,実行時に問題を起こす。

List 9.4.5 配列の動的再確保
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *ary,*tmp;
    int i;
    ary=(int *)malloc( 100 *sizeof(int));
    if (ary==NULL) {
        printf("ary[100]の確保に失敗しました\n");
        return (1);
    }
    for (i=0; i<100; i++) ary[i]=i;
  
    tmp=ary;
    ary=(int *)realloc(tmp, 200 *sizeof(int));
    printf("A ary=%p\n",ary);
    printf("A tmp=%p\n",tmp);
    if (ary==NULL) {
        printf("ary[200]の確保に失敗しました\n");
        free(tmp);
        return (1);
    }
    for (i=100; i<200; i++) ary[i]=i;
  
    tmp=ary;
    ary=(int *)realloc(tmp, 300 *sizeof(int));
    printf("B ary=%p\n",ary);
    printf("B tmp=%p\n",tmp);
    if (ary==NULL) {
        printf("ary[300]の確保に失敗しました\n");
        free(tmp);
        return (1);
    }
    for (i=200; i<300; i++) ary[i]=i;
  
    tmp=ary;
    ary=(int *)realloc(tmp, 400 *sizeof(int));
    printf("C ary=%p\n",ary);
    printf("C tmp=%p\n",tmp);
    if (ary==NULL) {
        printf("ary[400]の確保に失敗しました\n");
        free(tmp);
        return (1);
    }
    for (i=300; i<400; i++) ary[i]=i;
  
    tmp=ary;
    ary=(int *)realloc(tmp, 500 *sizeof(int));
    printf("D ary=%p\n",ary);
    printf("D tmp=%p\n",tmp);
    if (ary==NULL) {
        printf("ary[500]の確保に失敗しました\n");
        free(tmp);
        return (1);
    }
    for (i=400; i<500; i++) ary[i]=i;
  
    for (i=0; i<500; i++) printf("%4d",ary[i]);
    free(ary);
    return 0;
}

/* 実行結果
>realloctest.exe
A ary=003D96F0
A tmp=003D96F0
B ary=003DAA20
B tmp=003D96F0
C ary=003DAA20
C tmp=003DAA20
D ary=003DAA20
D tmp=003DAA20
   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19
  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39
  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59
  60  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79
  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99
 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
 */


 課題 9 その2

(6)配列の動的確保の手法と「エラトステネスのふるい」を用いてnまでの素数を求めるプログラムを作成しなさい。 (p09ex06.c)
ただし,nはプログラムが開始してから,キーボードより与えるものとし,nを受け取ってからふさわしい大きさの配列を動的に確保するものとします。(nは配列の大きさと一致するとは限らない。)

見つかった素数は,書式 "%7d" で表示し,10個ごとに改行しなさい。
またすべての素数を表示した後,改行して,見つかった素数の個数を次の書式で表示しなさい。
  "%d prime numbers are found.\n"

実行時にエラーが出る場合は,配列のサイズ以上の要素をアクセスしていないか確かめる必要がある。

提出ファイルではn=100,n=101,n=10000のときについて実行結果をすべて貼り付けなさい。
この3つの実行時には,実行時エラーが出ていないことを必ず確かめなさい。

ヒント 「エラトステネスのふるい」は「4.配列と文字・文字列」の課題(18)で作成しています。


 9.5 関数への構造体のアドレス渡し


一般に関数への引数に変数を渡す場合は,変数がコピーされる。構造体を関数へ引数として渡す場合も同様である。
構造体のアドレスのみ渡すことがある。
構造体のアドレスを渡すのには2つの理由がある。
(1)アドレスだけ渡すのならコピーが軽減される
(2)アドレスを渡して,呼び出し側の構造体を変更してほしい場合(これは,構造体を渡して,関数から戻るときに,構造体を持ち帰っているように見える) 
→9.5.4 List 9.5.5参照

9.5.1 構造体をコピーして渡引数と,アドレスのみ渡す引数

例えば
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

のもとで,ある点の座標をprintfで表示する関数を作り,テスト用のmain()でテストすると,次のようになるであろう。
この例は,構造体をコピーして引数として渡している例である。
もし構造体が,大きなバイト数を占める場合はCPUにとって負担になる。

List 9.5.1 構造体を引数に受け取る関数printPoint()

#include <stdio.h>

/*point_t型の定義*/
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

void printPoint(char str[], point_t point)
{
    printf("%s(%d,%d)\n",str,point.x,point.y);
}

int main()
{
    point_t pnt0={24,78};
    printPoint("pnt0=",pnt0);
    return 0;
}

/** 実行結果 **
pnt0=(24,78)
**************/


構造体のアドレスのみを渡すように変更するとCPUの負担は少なくなって次のようになる。

List 9.5.2 構造体のアドレスを引数に受け取る関数printPoint()

#include <stdio.h>

/*point_t型の定義*/
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

void printPoint(char str[], point_t *point)
{
    printf("%s(%d,%d)\n",str,(*point).x,(*point).y);
}

int main()
{
    point_t pnt0={24,78};
    printPoint("pnt0=",&pnt0);
    return 0;
}

/** 実行結果 **
pnt0=(24,78)
**************/


9.5.2 演算子
「->」の導入

List 9.5.2の関数printPointとまったく同じ機能でC言語特有な表現をすると次のようになる。
表現が異なる(記述が簡単になる??)だけで,動作は全くおなじになる。

List 9.5.3 構造体のアドレスを引数に受け取る関数printPoint()

#include <stdio.h>

/*point_t型の定義*/
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

void printPoint(char str[], point_t *point)
{
    printf("%s(%d,%d)\n",str,point->x,point->y);
}

int main()
{
    point_t pnt0={24,78};
    printPoint("pnt0=",&pnt0);
    return 0;
}

/** 実行結果 **
pnt0=(24,78)
**************/


9.5.3 修飾子「const」の導入

なお,次のように関数printPointを宣言しておくと,関数仕様がより明確になる。


List 9.5.4 構造体のアドレスを引数に受け取る関数printPoint()

#include <stdio.h>

/*point_t型の定義*/
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

void printPoint(char str[], const point_t * const point)
{
    printf("%s(%d,%d)\n",str,point->x,point->y);
}

int main()
{
    point_t pnt0={24,78};
    printPoint("pnt0=", &pnt0);
    return 0;
}

/** 実行結果 **
pnt0=(24,78)
**************/

説明
(1)const point_t *point
*pointはこの関数内では変化しない。すなわち
point->x=90;
などの作業は行なわれないので,呼び出し側は安心してよい。
(2)point_t * const point
pointはこの関数内で は変化しない。すなわち
point++
などの作業は行なわれない。(関数の内部を記述する際の自己規制)
(3)const point_t * const point
上記2つの条件を兼ね備え たpoint


9.5.4 アドレス渡しされた構造体の要素を変更する関数

List 9.5.5の関数 scalePoint(point_t *point, int scale)は,構造体のアドレスを渡されている。そのため,作業している構造体は呼び出した側が確保している構造体である。見かけ上,関数内で構造体要 素を変更して呼び出し側に返すことができているように見える。
これに対して,
scalePointNG(point_t point, int scale)は,呼び出し側の構造体のコピーが関数に渡されている。そのため,いくら構造体の値を変化させても,関数内の構造体の値が変化しているだけ で,呼び出し側の構造体に影響を与えることはできない。

List 9.5.5 構造体のアドレスを引数に受け取る関数scalePoint()は,構造体の中身を変更して返すことが出来る

#include <stdio.h>

/*point_t型の定義*/
typedef struct {
    int x; /*x座標*/
    int y; /*y座標*/
} point_t;

void printPoint(char str[], point_t *point)
{
    printf("%s(%d,%d)\n",str,point->x,point->y);
}

//pointの座標値をそれぞれscale倍するつもり(失敗する)
void scalePointNG(point_t point, int scale)
{
    point.x *= scale;
    point.y *= scale;
}

//pointの座標値をそれぞれscale倍する
void scalePoint(point_t *point, int scale)
{
    point->x *= scale;
    point->y *= scale;
}

int main()
{
    point_t pnt0={24,78};
    printPoint("A pnt0=",&pnt0);
    scalePointNG(pnt0,2); //x,yを2倍したつもりだが失敗
    printPoint("B pnt0=",&pnt0);  //値が変化していない
    scalePoint(&pnt0,2);
    printPoint("C pnt0=",&pnt0);
    return 0;
}

/
*************
A pnt0=(24,78)
B pnt0=(24,78)
C pnt0=(48,156)
**************/



9.5.5 配列を引数とする場合について

これまで配列を引数で関数に渡すと,関数から戻るときに,配列の中身を持ち帰ることができると説明してきた。
「配列の名前は配列の先頭番地である」という説明があったと思うが,配列を関数に渡すときは,配列の先頭番地が渡され,関数内で配列の値を変化させる作業をすると,実は呼び出し側の配列を変更していることになっていた。
このことを「
配列の中身を持ち帰ることができる」と説明していた。
さらに,配列ではない変数も,呼び出し側がアドレスを送って,呼び出される関数がポインタで受けると,関数内のその変数に関する作業は,実は呼び出し側の変数を変更することになり,
関数から戻るときに,その変数の中身を持ち帰ることができるという状況になる。

9.5.6 値渡しと参照渡し

通常の変数のように,コピーして関数に変数を渡す場合を,値渡しと言う。値渡しの場合,呼び出し側の変数は影響を受けないことが保証される。
これに対して変数のアドレスを渡して,呼び出し側の変数の値を変更してもらう場合を,参照渡しという。参照渡しの場合は,呼び出し側の変数は関数内で変更されてしまうことがある。意識的に関数に値を変更してもらう場合だけ参照渡しにすべきである。(これを避けるために前述のconstという表現がある。)

 課題 9 その3

(7)時間の和を求める関数を以下の指示に合うように作りなさい。 (p09ex07.c)

時間を表す構造体は次のようになる。
typedef struct {
    int hour; /*時間を表す*/
    int min; /*分を表す*/
    int sec; /*秒を表す*/
} hmstime_t;

時間の和を求める関数
hmstime_t addTime(hmstime_t *t1, hmstime_t *t2)
を作りなさい。

2時間46分27秒+1時間34分34秒の答えを求める時は
hmstime_t t1={2,46,27};
hmstime_t t2={1,34,34};
hmstime_t t3;
のもとで
t3=addTime(&t1,&t2);
と書けるようにしなさい。

次の検証用mainを使いなさい。

int main()
{
    hmstime_t t1={2,46,27};
    hmstime_t t2={1,34,34};
    hmstime_t t3;
    hmstime_t t4={2,46,27};
    hmstime_t t5={1,13,34};
    hmstime_t t6;
    t3=addTime(&t1,&t2);
    t6=addTime(&t4,&t5);
    printf("%2d:%2d:%2d\n",t3.hour,t3.min,t3.sec);
    printf("%2d:%2d:%2d\n",t6.hour,t6.min,t6.sec);
    return 0;
}



文章課題)ポインタに関するこのページの内容で重要なポイントをまとめてレポートにしなさい。ただし,初めの2行(ファイル名,ID,出席番号,氏名)は,これまでのプログラムと同様な書式で書くこと。 (p09.txt)



 9. 関数のラップ


ライブラリ関数をそのまま使うのではなく,ちょっと自分用にカスタマイズすると,自分の用途に適合した便利な関数を作ることができる。
ポインタを学習したので,このテクニックが自由に使えるようになった。
いくつかそのような例を見てみよう。

9.6.1 ライブラリ関数fgetc()とポインタFILE *fp

最初に復習。関数fgetc()を使って,ファイル内容をそのまま画面に出力してみよう。

list 9.6.1 ファイル読み込みと表示
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    int ch;
    fp=fopen("test1.txt","r");
    if (fp==NULL) exit(1);
    ch=fgetc(fp);
    while (ch!=EOF) {
        putchar(ch);
        ch=fgetc(fp);
    }
    fclose(fp);
    return 0;
}



このプログラムの動作は単純で,ファイルtext1.txtをそのまま表示している。

ファイルtest1.txtの内容
Hello
How are you.
@Good day!
I have a @bad!news.

処理結果の画面表示
Hello
How are you.
@Good day!
I have a @bad!news.

関数fgetc()のプロトタイプ宣言をよく見て,List 9.6.1の動作を確認しておこう。

int fgetc( FILE *fp )
ファイルストリームfpから1文字読み込みます。
返す値:処理が正常に終了した場合読み込んだ文字の文字コードをを返します。失敗した場合は EOF を返します。
引数:fp 読み込むファイルストリーム FILE構造体のアドレスが入っているポインタ変数
FILE *fp;
fp=fopen("filetoread.txt","r");
 :
ch=fgetc(fp);
のように使われる。


コラム 「 FILE *fp 」とは何だったのか

FILE型とは,ファイル読み書きのときに使われる構造体であ り,読み書きの対象を具体的なファイルの実態と結び付け,読み込みバッファや書き出しバッファのどこを作業しているのかのポインタなども内部に持ってい る。その内部構成はヘッダーファイルで定義されており,コンパイラ付属のライブラリに依存している。
fpはその構造体の先頭アドレスを保持しているポインタ変数である。
   fp=fopen("xxxxx.txt","r");
を行うと,関数fopen()はファイルxxxxx.txtを対象に読み込む作業をするための構造体を作り,そのアドレスを返してくる。
(構造体が作られるときには関数mallocなどが使われてメモリ確保が行われていることが推測される。)
もし,何らかの原因で,構造体が作れない場合はNULLポインタを返してくる。だからfp==NULLの場合には作業を打ち切るようにプログラムを書い た。そして,「fscanf(fp,"%d",&x)」のようにここでもまたポインタ変数fpを与えて読み込みを行った。
また,ファイルに対する作業が終わったときには「fclose(fp)」のようにポインタfpを渡して構造体を破棄していた。
(構造体が破棄されるときには関数freeが使われていることが推測される。)


9.6.2 ユーザ関数myfgetc()をつくる

ここで次のような課題を考えよう。

英数字でできたファイルから文字や改行コードを読み込んで画面に表示する。
ただし途中に「@」があったら,「!」が出てくるまでの間は表示しない。

例えばtest1.txtの処理は次のようになる。
ファイルtest1.txtの内容
Hello
How are you.
@Good day!
I have a @bad!news.

処理結果の画面表示
Hello
How are you.

I have a news.


すぐに思いつくのはfgetcを使って,1文字読む毎に「@」や「!」をチェックしながら表示する方法である。

list 9.6.2 ファイル読み込みと表示制御 その1
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    int ch;
    int nowork=0;
    fp=fopen("test1.txt","r");
    if (fp==NULL) exit(1);
    ch=fgetc(fp);
    while (ch!=EOF) {
        if (nowork==0) {
            if (ch=='@') {
                nowork=1;
            } else {
                putchar(ch);
            }
        } else {
            if (ch=='!') {
                nowork=0;
            }
        }
        ch=fgetc(fp);
    }
    fclose(fp);
    return 0;
}


これでよいのだが,プログラムの見通しが悪い。
そこで,fgetsと同じように使えるカスタマイズされた関数myfgetsを作り,この関数の中で
「@」や「!」をチェックしながら抜き取り作業をやってしまうと,
main側は「@」や「!」に関するルールに煩わされないようにすることが出来る。

list 9.6.3 ファイル読み込みと表示制御 その2
#include <stdio.h>
#include <stdlib.h>

int myfgetc(FILE *fp)
{
    int ch;
    ch=fgetc(fp);
    while(ch!=EOF && ch=='@') { //ここがifではなくwhileになっていることに注意*1
        ch=fgetc(fp);
        while(ch!=EOF && ch!='!') {
            ch=fgetc(fp);
        }
        ch=fgetc(fp); //*2
    }
    return ch;
}

int main()
{
    FILE *fp;
    int ch;
    fp=fopen("test1.txt","r");
    if (fp==NULL) exit(1);
    ch=myfgetc(fp);
    while (ch!=EOF) {
        putchar(ch);
        ch=myfgetc(fp);
    }
    fclose(fp);
    return 0;
}



*1 ここがif文だと,「xxxx@aaaaa!@bbbbb!yyyyy」という場合の連続除去がうまくいかない。
*2 ここに来る前のfgetcでEOFを受け取っていたとして(ファイルの終端に達した)も,
  ここのfgetcを実行してもEOFを受け取ることになる。

関数myfgetc()は次のようなプロトタイプ宣言となっている。

int myfgetc( FILE *fp )
ファイルストリームfpから1文字読み込みます。
返す値:処理が正常に終了した場合読み込んだ文字の文字コードを返します。失敗した場合は EOF を返します。
ただし,読み込み文字列中に「@」が見つかった場合,「!」が見つかるところまでを削除します。
引数:fp 読み込むファイルストリーム FILE構造体のアドレスが入っているポインタ変数
FILE *fp;
fp=fopen("filetoread.txt","r");
 :
ch=myfgetc(fp);
のように使われる。


関数myfgetc()はその内部に関数fgetc()を含んでおり,fgetcをラップ(包装紙で包んだという意味)した関数と呼ばれている。
関数myfgetc()を作成するのは面倒だが,作ってしまえば,main側の負担は極端に少なくなることがわかる。

同様なラップ関数は「7.3.3 テキストファイルの行単位処理」の例5の最後のところで
工夫して,改行コードなしで1行を読み込む関数fgets_modを作ろう。」で紹介している。
この関数は関数fgets()が行読込すると,行末の改行文字まで読み込んでしまうので,
行末の改行文字を含まずに1行読み込みする関数を作成し,便利に使えることを示している。

9.6.3 ユーザ関数fgettoken()を作る

ファイルから,文字列を文字列毎に取り出して画面表示したい場合がある。文字列の区切りは,スペース,改行,タブ(これらは複数個連続していても良い)とする。

例えば先のtest1.txtの処理は次のようにしたい。

ファイルtest1.txtの内容
Hello
How are you.
@Good day!
I have a @bad!news.

処理結果の画面表示
Hello
How
are
you.
@Good
day!
I
have
a
@bad!news.

このように,区切り文字で区切られた1つ1つの文字列はトークンと呼ばれる。

fgetcをラップして,トークンの切り出し関数fgettoken()を作ってみよう。

関数fgettoken()は1行読み出し関数
fgets()と同じ形(インタフェイス)をしている。
ここで使われている関数isspace()は空白やタブ,改行コードに対して非0を返す(それ以外は0を返す)関数である。

list 9.6.4 ファイルからのトークンの読み込み
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> // isspace()のため
 
char *fgettoken(char *buff, int size, FILE *fp)
{
    int ch;
    int crr=0;
    ch=fgetc(fp);
    while (ch!=EOF && isspace(ch)) { //isspace()はchが区切り文字かどうかをチェックする関数
        ch=fgetc(fp);
    }
    while (ch!=EOF && !isspace(ch)) {
        if (crr<size-2) {
            buff[crr++]=ch;
        } else {
            break;
        }
        ch=fgetc(fp);
    }
    if (ch==EOF && crr==0) {
//ファイル終端に達した場合への対処
         return NULL;
    } else {
        buff[crr]='\0';
        return buff;
    }
}

#define SIZE 256

int main()
{
    FILE *fp;
    char buff[SIZE];
    char *ptr;
    fp=fopen("test1.txt","r");
    if (fp==NULL) exit(1);
    ptr=fgettoken(buff,SIZE,fp);
    while (ptr!=NULL) {
        puts(buff);
        ptr=fgettoken(buff,SIZE,fp);
    }
    fclose(fp);
    return 0;
}




ライブラリ関数fgets()のプロトタイプ宣言とユーザ関数fgettokenの定義を比べてみよう。

char *fgets( char *buff, int size, FILE *fp )
char *fgets( char buff[], int size, FILE *fp ) と同じ
ファイルストリームfpから1行読み込み,保存領域buffに保存します。ただし保存領域の大きさはsize byteです。
返す値:処理が正常に終了した場合,保存領域の先頭アドレスを返します。失敗した場合は ポインタ定数NULL を返します。
char *fgettoken(char *buff, int size, FILE *fp)
ファイルストリームfpから1トークン読み込み,保存領域buffに保存します。ただし保存領域の大きさはsize byteです。
返す値:処理が正常に終了した場合,保存領域の先頭アドレスを返します。失敗した場合は ポインタ定数NULL を返します。





9.6.4 関数fgettoken()内で関数fgetc()を関数myfgetc()に置き換える

トークンの取り出しで,もし,入力ファイルの
途中に「@」があったら,「!」が出てくるまでの間は捨てて,作業には組み入れないというルールが追加された場合を考えてみよう。
関数
fgettoken()内で使われている関数fgetc()を,先に作成した関数myfgetc()に差し替えるだけでこの作業ができる。

関数
fgettoken()や関数myfgetc()を使わずに,ライブラリ関数fgetc()のみを使った力わざでmain内でこの作業を行うとしたら,結構難しい課題となってしまうだろう。
List 9.6.5では無視するルールを実現する関数,区切り文字で区切った文字列を取り出す関数に分けて作業を行っているので,見通しの良いプログラムを書くことが出来る。

list 9.6.5 ファイルからのトークンの読み込み(@!間を無視するルール追加)
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> // isspace()のため

int myfgetc(FILE *fp)
{
    int ch;
    ch=fgetc(fp);
    while(ch!=EOF && ch=='@') {
        ch=fgetc(fp);
        while(ch!=EOF && ch!='!') {
            ch=fgetc(fp);
        }
        ch=fgetc(fp);
    }
    return ch;
}

char *fgettoken(char *buff, int size, FILE *fp)
{
    int ch;
    int crr=0;
    ch=myfgetc(fp);
    while (ch!=EOF && isspace(ch)) {
        ch=myfgetc(fp);
    }
    while (ch!=EOF && !isspace(ch)) {
        if (crr<size-2) {
            buff[crr++]=ch;
        } else {
            break;
        }
        ch=myfgetc(fp);
    }
    if (ch==EOF && crr==0) { //
ファイル終端に達した場合への対処
         return NULL;
    } else {
        buff[crr]='\0';
        return buff;
    }
}

#define SIZE 256

int main()
{
    FILE *fp;
    char buff[SIZE];
    char *ptr;
    fp=fopen("test1.txt","r");
    if (fp==NULL) exit(1);
    ptr=fgettoken(buff,SIZE,fp);
    while (ptr!=NULL) {
        puts(buff);
        ptr=fgettoken(buff,SIZE,fp);
    }
    fclose(fp);
    return 0;
}

実行結果
Hello
How
are
you.
I
have
a
news.

おまけ
もし,取り出したトークン(文字列buff)が数字だけでできていて,これをint型の変数varに取り込みたかったら,
    sscanf(buff,"%d",&var);
のようにするとよい。