第5回目 ポインタ・その1(5/08)

今日のキーワード

■ メモリアクセス

計算機は,基本的に以下の3つで構成される. 計算機が動作するためには,たとえば,「CPUがメモリからプログラムコードを1つずつ読み,CPU内部のレジスタ(変数)を使って演算し,結果をメモリに書き出す」という処理をする.I/Oは,画面表示やネットワークボードを制御する部分である.制御用のデータは,メモリと同様のアドレス空間の一部,あるいは,I/Oポートを通じて読み書きする.このことから,プログラマは,基本的には,CPUが効率よくメモリの読み書きをすることを考えなければならない.

□ メモリの構造

メモリは,0か1を書き並べる場所である.メモリ空間という. 0/1を横一列に並べてみると,メモリの大きさがわかる.

CPUは,メモリの一部分しか読み書きできない. したがって,メモリ空間のどの場所を読み書きするのかを指定しないといけない.

1ビットごとに場所を決めることは効率が悪いので, 8ビットを1バイトと呼び,1バイトごとにメモリ空間上の番地が決められている. これをアドレスという.

□ メモリに文字列を書き込む

たとえば,102,478 番地を起点に,"abc" という文字列を格納すると,以下の図のようになる.

102,478番地から102,481番地は,"abc"(NULL文字含む)のためのメモリ空間であるが,それ以外の部分は未知である.したがって,メモリの読み書きは,何番地から何番地までを使って良いのか,十分に注意を払う必要がある.

■ 変数との関係

□ 配列変数

char s[4] により配列変数を宣言すると,適切な番地を起点に,上の図のようにメモリ空間が使われている.

配列変数の宣言の時の [4] は,起点番地を含めて幾つまでを使うか,配列の大きさを表す.

配列変数への読み書きの時の [1]は,配列番号といい,起点番地からの相対的な番地を表す.したがって,C言語のプログラマは,起点番地の数字を意識する必要はなく,s[0]='a'; s[1]='b'; s[2]='c'; s[3]='\0'; という操作により文字列を格納すれば良い.

□ ポインタ

char *p により文字へのポインタ型変数を宣言すると,変数 p には,番地の数字を直接代入することになる.したがって,上の図において,p=102478 と代入し,*p='a' とすると,その番地の 0/1 を「0110001」とすることができ,'a'を代入したことになる.

ポインタ型変数に +1 をすると,番地の数値を 1 データぶんだけ次の番地を指すようになる.char 型の場合,1バイトぶんの大きさである.したがって,char 型へのポインタ変数に +1 をすると番地番号も1つだけ増える.しかし,int 型の場合 4 バイトぶんの大きさであるので,int型へのポインタ変数に +1 をすると番地番号は4つ増える.

しかしながら,C言語のプログラマは,番地番号が4つ増えるのか1つ増えるのかは,気にする必要はない.char *p や int *q などのように,プログラム上では,char や int という意味でポインタを使うので,コンパイラーが+4なのか+1なのかを判断するからである.

□ 変数宣言

変数宣言をすると,変数の値を格納するアドレスが決められる.ポインタ型の変数も,あるアドレス値を格納する必要があるので,そのためのアドレスが決められる.

■ メモリアロケーション

int x,y; char str[10]; などは,プログラムで宣言すれば,メモリが使えるようになる(上記の例では,char c1 の値は,105,022 に格納することに決まること.). しかし,char *s1; などのようにポインタ変数を宣言しただけでは,番地を格納する場所は決まっても,文字列を格納する場所は決まっていない. malloc というC言語の関数は,何番地から使ってよいのかを出力する関数である. たとえば,以下のプログラムによると,10個のchar型データを格納する起点番地を返してくる.malloc の左にある (char *) は,char を格納するはずの番地であることを明示している(malloc の返り値は,何型へのポインタであるかは明示されていないので,char *であると定めている.これをキャストという.).
char *s;
s = (char *)malloc(sizeof(char)*(10));

□ 演習

  1. 全角文字に対応した「さかさ文字列」を出力する関数 zenkakurev を作成せよ.引数は元の文字列,返り値はさかさ文字列とする.⇒ prac0501.c
    /* prac0501.c */
    #include <stdio.h>
    #include <string.h>
    
    char *zenkakurev(const char str[])
    {
    
       ここにプログラムを書く
    
    }
    
    main()
    {
      char str1[BUFSIZ];
      char *str2;
    
      fgets(str1,BUFSIZ,stdin);
      if(str1[strlen(str1)−1] == ’\n’ )
        str1[strlen(str1)−1] = ’\0’;
      str2 = zenkakurev(str1);
      printf(”%s\n”,str2);
      free(str2);
    }
    
  2. 2つの文字列を結合する関数 mystrcat を作成せよ.mystrcat は,文字列である引数を2つ持ち,それぞれの文字列の長さを求め,その合計値だけメモリをmallocで確保する.そして,結合した文字列をそのメモリに格納する.返り値は,確保したメモリのアドレスとする.なお,確保に失敗した場合は,NULL を返す.⇒ prac0502.c

■ 宿題

半角数字を全角数字に置き換え,それ以外はそのままコピーする関数 zenkakunum を作成せよ.zenkakunum は,文字へのポインタ型の引数を1つ持つ.まず,この引数の文字列の先頭から末尾までについて,半角数字の数を調べ,全角数字に置き換えた際の文字列の長さを計算する.そして,丁度の容量を確保し,置き換え結果を格納する.返り値は,置き換え後の文字列の先頭のポインタとする.なお,確保に失敗した場合は,NULL を返す.⇒ prac0503.c

(c) Masato TOKUHISA, 2003, May. 2