Try T.M Engineer Blog

「アウトプットする事は大事だ」と思って初めたブログ、プログラミング、独り語り、etc

Cで配列をポインタ変数に格納する方法について復習してみた

今度はCで配列をポインタに格納する方法について復習してみました。
復習したラインナップは以下の通り。

 1.配列をポインタ変数に入れる
 2.配列をポインタ変数に入れる時のイケてない例
 3.2次元配列をポインタ変数に入れる

配列をポインタ変数に入れる

前回の記事でも少し触れていましたが、Cで配列をポインタ変数に格納すると配列の先頭のアドレスがポインタ変数に格納されます。
そのため、ポインタ変数へ格納した後は、ポインタ変数に+1することで配列の次の値を参照することができます。
以下、例です。

int *data1;
int input1[3]={1,2,3};

 for(i=0;i<3;i++){
   printf("adress of input1[%d] = %p\n",i,&input1[i]);
 }

data1=input1;

printf("adress of *data1 = %p\n",&data1);

for(i=0;i<3;i++){
    printf("adress of data1 = %p\n",data1);
    printf("value of data1 = %d\n",*data1);
    ++data1;
}

実行結果は以下の通り。

adress of *data1 = 0x7fff51cfcb08
adress of data1 = 0x7fff51cfcb5c
value of data1 = 1
adress of data1 = 0x7fff51cfcb60
value of data1 = 2
adress of data1 = 0x7fff51cfcb64
value of data1 = 3

配列の先頭のアドレスがポインタ変数に格納されるのがわかります。 また、ポインタ変数に+1することで、配列の次の値が参照できる事もわかります。

図で書くと以下のような感じです。 f:id:special-moucom:20161228235911j:plain

配列をポインタ変数に入れる時のイケてない例

素直な人ならCで配列を使う時には上記の方法を使うでしょう。
しかし、中には「配列を使ってるんだからポインタも配列で使えないの?」と思う方もいると思います。

結論から言うと可能でした。というわけで、以下やってみました。

int *data2[3];
int input1[3]={1,2,3};

for(i=0;i<3;i++){
    data2[i]=&input1[i];
    printf("adress of *data2 = %p\n",&data2[i]);

    printf("adress of data2 = %p\n",data2[i]);
    printf("value of data2 = %d\n",*data2[i]);
}

実行結果は以下の通り。

adress of *data2 = 0x7fff51cfcb20
adress of data2 = 0x7fff51cfcb5c
value of data2 = 1
adress of *data2 = 0x7fff51cfcb28
adress of data2 = 0x7fff51cfcb60
value of data2 = 2
adress of *data2 = 0x7fff51cfcb30
adress of data2 = 0x7fff51cfcb64
value of data2 = 3

問題なく使えますし、動きます。
しかし、わかった事があります。
 「これではポインタを使う意味がないし、無駄が多い...」
Cでポインタを使う目的の1つに「使用するメモリを削減できる」というのがあります。 上記で、配列を1つのポインタ変数に格納できるというのがありましたが、1つのポインタ変数で済むものを わざわざ配列と同じ数のポインタ変数を用意して、全て格納するというのは無駄でしかありません。 図で書くと以下のような感じです。

f:id:special-moucom:20161229000253j:plain

ちなみに、メモリは変数1つ宣言するだけで使われますし、配列もその数が多ければ多いほどメモリを消費します。 使用するメモリが多ければ、それだけ処理が遅くなりますので、この様な使い方は避けるべきです。

2次元配列をポインタ変数に入れる

最初に言います。
僕はCで2次元配列を使った事がありません。

初めてサンプルコードを書いてみた感想としては、色々な書き方ができるんだなぁと驚いたこと。
後にも書きますが、2次元配列といつつメモリ上は直列になっていることに驚きました。

というわけで、サンプルコードです。

int (*data3)[3];
int input2[2][3]={{4,5,6},{7,8,9}};

data3=input2;

printf("adress of *data3 = %p\n",&data3);
printf("value of data3 = %p\n",data3);
printf("adress of *data3 = %p\n",&data3+1);
printf("value of data3 = %p\n",data3+1);

for(i=0;i<2;i++){
  for(k=0;k<3;k++){
    printf("adress of data3 = %p\n",&data3[i][k]);
    printf("value of data3 = %d\n",data3[i][k]);
  }
}

実行結果は以下の通り。

adress of *data3 = 0x7fff5d7f1b00
adress of *data3 = 0x7fff5d7f1b08
adress of data3 = 0x7fff5d7f1b40
value of data3 = 4
adress of data3 = 0x7fff5d7f1b44
value of data3 = 5
adress of data3 = 0x7fff5d7f1b48
value of data3 = 6
adress of data3 = 0x7fff5d7f1b4c
value of data3 = 7
adress of data3 = 0x7fff5d7f1b50
value of data3 = 8
adress of data3 = 0x7fff5d7f1b54
value of data3 = 9

2次元配列を格納するポインタ変数は「int (data3)[3];」です。
上記で記載した「int
data2[3];」との違いは以下の通りです。

 ① int *data2[3]   ・・・ int型のポインタ変数data2を3つ作成する。  
 ② int (*data3)[3] ・・・ int型のポインタ変数data3を3つ分のアドレス格納エリアを確保した状態で作る。  

伝わらない方は以下の図を見てください。
①と②で見比べると違いがわかると思います。また、こうやって見ると2次元配列といつつメモリ上は直列になっていることもわかります。

f:id:special-moucom:20161229002654j:plain

という感じで、復習してみました。
「2次元配列といつつメモリ上は直列になっている」という事がわかったので、こんな事も試してみました。

int *data2[3];
int input2[2][3]={{4,5,6},{7,8,9}};

for(i=0;i<2;i++){
  data2[i]=&input2[i][0];
}

for(i=0;i<2;i++){
  for(k=0;k<3;k++){
    printf("value of data2 = %d\n",data2[i][k]);
  }
}

実行結果は以下の通り。
配列なので先頭アドレスさえわかってしまえば、2次元配列でもいろいろな書き方ができるという事です。

value of data2 = 4
value of data2 = 5
value of data2 = 6
value of data2 = 7
value of data2 = 8
value of data2 = 9

ではでは...