第8回 プログラミングについて 『配列の話し』

『配列の話し』
配列の話し。BASICやFORTRANを始めて乗り越えなければならないのがサブルーチンと配列。C言語などを始めて乗り越えなければならないのが、このサブルーチン(C言語では関数)と配列の他にポインタと構造体。特に今回はこの配列について考えてみましょう。
私がプログラムを書き始めた頃、後輩のS君に「プログラムは配列とサブルーチンが分れば完璧だぜ!」と言われたのを覚えています。確かに配列が使えなければプログラムを書くのはかなり厳しいでしょう。もし配列を使わないでプログラムを書くとなれば『1~10個の整数を逆に並べる』というものを作ると、
#include <stdio.h>
main()
{
int i1,i2,i3,i4,i5,i6,i7,i8,i9,i10;
int t;
i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5;
i6 = 6; i7 = 7; i8 = 8; i9 = 9; i10=10;
printf("%d\n",i1);
printf("%d\n",i2);
printf("%d\n",i3);
printf("%d\n",i4);
printf("%d\n",i5);
printf("%d\n",i6);
printf("%d\n",i7);
printf("%d\n",i8);
printf("%d\n",i9);
printf("%d\n",i10);
t=i1; i1=i10; i10=t;
t=i2; i2=i9 ; i9 =t;
t=i3; i3=i8 ; i8 =t;
t=i4; i4=i7 ; i7 =t;
t=i5; i5=i6 ; i6 =t;
printf("%d\n",i1);
printf("%d\n",i2);
printf("%d\n",i3);
printf("%d\n",i4);
printf("%d\n",i5);
printf("%d\n",i6);
printf("%d\n",i7);
printf("%d\n",i8);
printf("%d\n",i9);
printf("%d\n",i10);
}
となって、厳しいものがあります。もし100個とか1000個とかになれば降参の一言となってしまいます。
それで配列が登場する訳ですが、配列とは一体何者なのでしょうか。配列とは同じ型の変数が連続しているものとでも言えるかと思います。例えば、
int data[100];
と定義すると、data は int 型の変数が100個連続したものになります。実際のコンピュータのなかで実行するときには変数名 data が本当に存在するのではありません。
メモリー上に200バイトまたは400バイトの領域が確保され、そこを data の配列として使用される訳です。
C言語ではその要素をアクセスするには、
data[0]
とすれば data の最初の要素となります。FORTRANでは、
INTEGER DATA(100)
DATA(1)
と書き、これも data の最初の要素を示します。最初の要素をC言語では0番目でFORTRANでは1番目と表現しなければならないか疑問に思うかも知れませんが。これは言語の性格上こうなってしまった訳で、FORTRANでは科学計算向きな言語で自然界での数の数え方と同じく1番目が最初としたのです。C言語ではどちらか言えばシステム記述用の性格とポインタの関係で0番目を最初としたのです。人間にとってみれば1番目から始まる方が理解しやすいのですが、これも仕方のないことでしょう。
FORTRANなどでは実際のアドレス計算には1から始まっているので次の様に1を引いて計算します。
DATA のアドレスを D、サイズをS、配列の要素をNとすると、最初の要素のアドレス Aは、
A=D+(N-1)*S
となります。
話しを元に戻しましょう。最初の例を配列を使って書くと、
#include <stdio.h>
#define MAX_DATA 10
main()
{
int data[MAX_DATA];
int i,j,t;
for(i=0;i<MAX_DATA;i++)data[i]=i+1;
for(i=0;i<MAX_DATA;i++)printf("%d\n",data[i]);
for(i=0,j=MAX_DATA-1;i<MAX_DATA/2;i++,j--){
t =data[i];
data[i]=data[j];
data[j]=t;
}
for(i=0;i<MAX_DATA;i++)printf("%d\n",data[i]);
}
となります。100個とか1000個にするには MAX_DATA を変えるだけで済んでしまいます。
次に多次元のでも同様なのですが、1つだけ気を付けておかなければならないことは要素がどの様にアクセスされるかです。C言語で2次元の配列を次のように作ると、
int data[100][10];
要素は次の様に並びます。
[0][0] [0][1] [0][2] [0][3] [0][4] [0][5] ...
[1][0] [1][1] [1][2] [1][3] [1][4] [1][5] ...
[2][0] [2][1] [2][2] [2][3] [2][4] [2][5] ...
:
:
ようするに、[0][0]の隣には[0][1]が並びます。
ところがFORTRANでは
INTEGER DATA(100,10)
とすると
(1,1) (2,1) (3,1) (4,1) (5,1) (6,1) ...
(1,2) (2,2) (3,2) (4,2) (5,2) (6,2) ...
(1,3) (2,3) (3,3) (4,3) (5,3) (6,3) ...
:
:
となり (1,1)の隣には(2,1)が並ぶのです。
この違いは、C言語では文字列は配列で表現されるので[0][0]の隣が[0][1]でないと不都合で、FORTRANは科学計算でよく使うX、Y、Zの配列を自然に表現するには(1,1)の隣が(1,2)でないと不都合になってしまうのです。FORTRAN77では文字変数が使用できたので不自由はなかったのですが、もしFORTRANの多次元配列の並びで文字列を配列で表現したら大変なことになってしまうでしょう。
配列を使うときに注意しなければならないことがあります。存在しない要素をアクセスしないようにしなければなりません。
int data[10];
data[10]=100;
と書くと、コンピュータは素直に data の11番目に 100 を代入します。この架空の11番目のアドレスが何かで結果は違いますが、今までの経験ではこの代入文では何事も問題は発生せず、後の処理で変な動作をしてしまうのです。運が良ければ何事も起きません。
文字配列の任意の位置に実数値や整数値を代入したいときもあります。次の様な例は可能なことがありますが、この危険性は知っておいた方が良いでしょう。
#include <stdio.h>
main()
{
char data[20];
put (data+1,100.0);
show(data+1);
}
put(to,from)
float *to,from;
{
*to=from;
}
show(f)
float *f;
{
printf("%f\n",*f);
}
この例は文字配列として20バイトと作っておき、data[1] から4バイトに実数値の100.0を代入して表示するものです。理屈では可能なのですが、この危険性はコンピュータによっては正常に動作しないことがあるのです。実際にPC98やVAXでは動作しますが、HPのワークステーションではコアダンプ(異常停止)してしまいましす。
この理由はコンピュータのアーキテクチャーに関係するもので、バイトアラインメントが可能なものでは正常に動作し、そうでないものは異常を起こすのです。この例で異常が発生するコンピュータでは、変数はワード単位(通常2バイト単位)またはロングワード単位(通常4バイト単位)のアドレスに存在しなければなりません。上の例ではdata は正しいアドレスに配置されますが、 data+1 はそうではなくなります。1文字単位で扱うのであれば問題はありませんが、整数や実数のときにはアーキテクチャーの約束違反になってしまうのです。
どうしてもこの様な事を行ないたいときには、文字単位で操作することです。例えば関数 put を次のようにします。
put(to,from)
float *to,from;
{
char f[sizeof(float)];
int i;
*(float *)f=from;
for(i=0;i<sizeof(float);i++){
((char *)to)[i]=f[i];
}
}
FORTRAN77では文字型があって、文字列の扱いは一般の数式と似た使い方ができて便利なものです。しかし、先ほども述べましたがC言語では文字列型などの型はないので、これも配列で表現し文字列を扱う関数を使用して操作します。慣れてしまえば違和感はないのですが、やはり抵抗はあります。
例えば、文字列を連結して比較するときには、FORTRAN77では、
CHARACTER A*10,B*10,C*20
:
:
C=A//B !文字変数 A と B を連結して C に代入。
IF(C.EQ.'ABCDEFG')THEN !文字変数 C と文字定数 'ABCDEFG' と比較
:
:
END IF
と簡潔に表現できますが、C言語では、
char a[10],b[10],c[20];
:
:
strcpy(c,a); /* 文字列 a を c にコピー */
strcat(c,b); /* 文字列 b を c に連結 */
if(strcmp(c,"ABCDEFG")==0){ /* 文字列 c と文字列定数 "ABCDEFG" を比較 */
:
:
}
となり、関数の呼びまくりになってしまうのです。これもC言語の思想の問題なので仕方がないのかなと、あきらめてはいますが不満は残っています。
それではまた次回も付き合って下さい。