配列

配列とは,同じデータ型の値の集合のためのデータ構造である.第2回において学習した文字列は,文字型の値を集合であった.ここではより一般的な配列について学習する.さらに,添字を拡張した多次元配列についても学習する.

1次元配列

宣言方法

C言語では,配列はその要素数とともに宣言するという決まりがある.以下の例は,int型の要素6個からなる配列scoreを宣言する例である.

int score[6];

このように宣言された配列は,メモリ上の連続した領域に確保される.処理が進み,不要になった段階で確保されていた領域は自動的に解放される.

変数の有効範囲を抜けた段階

添字による各要素へのアクセス

配列の各要素には,以下のように大かっこを使った添字によってアクセスする.添字として有効なのは,整数定数および整数式である.浮動小数点数は使えない.

score[1] = 83;
printf("%d\n", score[2]);
score[2*i + 1] = 100;

初期化方法

配列を宣言しただけでは,各要素の値は不定である.プログラム開発において,不定値をそのままにしておくことは思わぬバグの混入を招くリスクが高いため,通常は宣言の直後に初期化を行うことが強く推奨される.初期化の典型的なパターンをリスト 1に示す.

リスト 1. 配列の初期化方法
int data[10];
int i;
for (i = 0; i < 10 /* 配列の要素数 */; ++i) {
  data[i] = 0;
}

また,宣言と初期化を同時に行う方法(リスト 2)も許されている.この場合,要素数を省略することも可能である.特に,固定文字列の初期化などに有用である.

リスト 2. 宣言と同時に初期化する方法
double val[4] = {0.3, -5.1, 102.55, 0.0};
int data[] = {8, -1, 7, 10, 255, -512, 1};  // 要素数を省略する場合
char name[] = "Takushoku Taro";

多次元配列

配列の添字は,多次元に拡張することができる.これによって,例えば2次元データなどに,x, yなどといった座標を添字として用いてアクセスすることが可能となる.多次元に拡張されるのは添字のみであり,データそのものが確保されるメモリ上では常に1次元のアドレスが割り当てられることに注意.

2次元配列の例をリスト 3に示す.

リスト 3. 2次元配列の使用例
int data[2][3];  // 2行3列の2次元配列を宣言,添字の有効範囲はdata[0-1][0-2]
int rows = 2;    // 行数(縦の要素数)
int cols = 3;    // 列数 (横の要素数)
int i, j;

// 初期化
for (i = 0; i < rows; ++i) {
  for (j = 0; j < cols; ++j) {
    data[i][j] = 0;
  }
}
// 各行の先頭アドレスを表示
for (i = 0; i < rows; ++i) {
  printf("%x\n", &data[i][0]);
}
// 各行の先頭アドレスを表示(省略形)
for (i = 0; i < rows; ++i) {
  printf("%x\n", data[i]);
}
// (x,y) = (2,1)の要素に値を代入
data[1][2] = 100;

2次元配列においても,宣言と同時に初期化を行うことが可能である.

リスト 4. 2次元配列を宣言と同時に初期化する方法
int score[3][5] = {
    {70, 80, 50, 99, 60},
    {60, 45, 70, 88, 30},
    {90, 85, 70, 57, 85}
};

リスト 4で初期化された2次元配列を図示したものが図 1である.図の上段は,各要素に対する2次元の添字を示したものである.同じ色の添字は同じ色の次元に対応する.また,図の下段は,この2次元配列のメモリ上に占める領域の概念図である.各行の先頭アドレスの書き方およびその省略形が示されている.

{half-width}
図 1. 2次元配列score

次のリストは,この2次元配列scoreの行ごとの要素の合計値を求めるプログラムの例である.

リスト 5. 2次元配列の行ごとの要素の合計値を求める例
// 配列score[3][5]は定義されていると仮定
int i, j;
int row_sum[3];  // 行ごとの合計値を格納するための配列

// 初期化
for (i = 0; i < 3; ++i) {
  sum[i] = 0;
}

for (i = 0; i < 3; ++i) {
  for (j = 0; j < 5; ++j) {
    sum[i] += score[i][j];
  }
}
変数の有効範囲(スコープ)

C言語では,一部の例外を除いて,変数には有効範囲が存在する.有効範囲を超えて変数に値を代入したり,その値を参照したりすることはできない.具体的には,

  • 変数宣言の直後から,その変数宣言が含まれる{に対応する}の直前まで

が,有効範囲となる.変数名を用いて値を代入・参照できるのは有効範囲内に限られる.処理が有効範囲を超えると,それらの変数に使われていたメモリ領域は解放され,名前は消去される.

このように管理される変数を 自動変数(auto変数)と呼ぶ.自動変数はメモリ上の スタックと呼ばれる領域に確保される.

以下のリスト 6において,8行目の変数a1への代入は有効範囲を超えているためコンパイルエラーとなる.

リスト 6. 変数の有効範囲の確認
int main() {
  int a0 = 5;
  {
    int a1 = 10;
    printf("a0 = %d, a1 = %d\n", a0, a1);
  }
  a0 = 0;
  a1 = -1;  // 範囲外のためコンパイルエラーとなる
}
グローバル変数

有効範囲を超えてアクセスしたい変数を用意する必要がある場合,それらを グローバル変数として宣言する.グローバル変数の宣言は,main関数が始まる前までに変数宣言を行う.リスト 7にグローバル変数の使用例を示す.

リスト 7. グローバル変数
int g_a1 = 0;  // グローバル変数
int main() {
  int a0 = 5;
  printf("a0 = %d, g_a1 = %d\n", a0, g_a1);
  a0 = 0;
  g_a1 = -1;
  printf("a0 = %d, g_a1 = %d\n", a0, g_a1);
}

リスト 7の実行結果は以下のとおりである.

a0 = 5, g_a1 = 0
a0 = 0, g_a1 = -1

自動変数とグローバル変数で,同じ名前を使用した場合,自動変数が優先される.ただし,非常に読みにくいソースコードとなるため,一般にはグローバル変数には,ひと目でそれとわかる名前を使用することが推奨される.

リスト 8. 変数の優先順位の確認
int a = 0;
int main() {
  int a = 100;
  printf("a = %d\n", a);  // a = 0と表示される
}
変数のサイズ

各データ型の値1つをメモリに確保するのに必要なデータ量をその変数のサイズという.表 1に,Windows 64bitマシンでの各データ型のサイズを示す.配列として宣言する場合には,これらのサイズに要素数が乗算されることに注意.例えば,100個の要素を持つint型,の配列は 4×100 = 400 Byteのサイズとなる.

表 1. データ型とサイズ
データ型 サイズ(Byte)

char

1

int

4

float

4

double

8

異なるOS等におけるデータ型のサイズを知るには,sizeof演算子を用いる.例えばdouble型のサイズを得るには,sizeof(double)のように記述する.

自動変数として宣言される配列の要素数が非常に大きな値となる場合には注意が必要である.スタック領域の大きさは,一般にそれほど大きくはないため,GBクラスのサイズの配列はそスタック領域に収まらないことがある.この場合,stack overflowというエラーになる可能性がある.そのため,巨大な記憶領域が必要な場合には,スタック領域ではなく,ヒープ領域を用いることが普通である

本講義の範囲を超えるため,興味のある学生は教員へ質問してください.