配列

多数のデータを手軽に取り扱うため,配列の適切な使い方を理解しよう.

教科書の該当範囲:第7章,第16.2節

基礎知識

配列の宣言

複数の同型の変数の集合を一気に用意できる.

ソースコード記述方法:

型名 配列名[要素数];

具体例:

int  data[100];

これで,100個の int型の要素変数 data[0],data[1],data[2],...,data[99] から成る配列 data が用意される. なお,要素数 100 と要素番号 0〜99 との違いに注意しよう.

配列 a[N] の場合...


マクロの定義

ソースコード内の数値に名前を付けておける.

ソースコード記述方法:

#define  マクロ名  数値

具体例:

#define	N 100	// マクロ名 N として数値 100 を定義

int  data[N];	// コンパイル時に int data[100]; へ書き換えられる

N = 0;		// NG.コンパイル時に 100 = 0; へ書き換えられ,エラーとなる

コンパイル時にソースコード中のマクロ名 N が 定義内容の数値 100 へ自動的に書き換えられる. マクロは,変数と似ているように見えるが,まったく異なり, プログラム実行時に内容(数値)の変更はできない. プログラム実行中に不変であるべき数値を ソースコード全体で一貫的に矛盾なく記述したい場合に利用する.


実習の準備

いつもの通り,作業用ディレクトリを準備しよう:

$  cd
$  mkdir  mtr-0601
$  cd  mtr-0601

データ編集プログラム

複数データの入力・修正が可能なプログラム record.c を作成してみよう. (前回の平均計算プログラムでは,入力データについて, 最新のもの1個以外を捨てていた. 今回は入力データをすべて残す.)

Step 1. 基本プログラム(配列データの表示)

データ入力については後回しとして, データ表示の機能だけ作り, とりあえず,配列の本的な使い方だけ確認してみよう:

$  vim  record.c
または
$  gedit  record.c  &
#include <stdio.h>

int main(void)
{
	int	data[3];	// データ記録用の配列
	int	i;		// 要素番号のカウンタ

	printf("データ:");
	for (i = 0; i < 3; i++) {
		printf("%d ", data[i]);	// 配列要素の数値を表示
	}
	printf("\n");		// 改行

	return (0);
}
$  cc  record.c  -o  record
$  ./record
...

普通の変数と同様,配列要素にもゴミが入っている. 大抵の場合,初期化が必要になる. ソースコードの配列宣言の部分を 次のそれぞれの行と差し替えて試してみよう:

//	int	data[3];
	int	data[3] = {1, 2, 3};	// 初期値を一気に指定できる
//	int	data[] = {1, 2, 3};	// 初期値を指定したら要素数は省略できる
//	int	data[3] = {1, 2, 3, 4, 5};	// 初期値が多すぎ...これはNG
//	int	data[3] = {1, 2};	// 初期値は少ないが...これはOK
//	int	data[3] = {};		// これもOK.初期値を一気にゼロにできる

Step 2. 配列データの入力・修正

データを順番に入力できるようにしてみよう;

...
	int	data[3] = {};
...
	for (i = 0; i < 3; i++) {
		printf("%d番目の整数データ > ", i);
		scanf("%d", &data[i]);	// 要素 data[i] へデータを入力
	}

	printf("データ:");
...

さらに,要素番号も指定してデータ入力できるようにしてみよう:

...
//	for (i = 0; i < 3; i++) {
	while (1) {
		printf("要素番号(-1 で終了)> ");
		scanf("%d", &i);	// 要素番号を入力
		if (i < 0) break;

		printf("%d番目の整数データ > ", i);
		scanf("%d", &data[i]);
//	}

		printf("データ:");
		...
	}
	return (0)
...

これで,データの書き換えも自由にできるようになった. しかし,要素番号として大きすぎる数値を入力してしまうと... 実行時エラーによって強制終了させられたり, 異常動作に陥ることもある. (いろいろな数値で試すこと.)

原因は,配列用のメモリ領域の範囲外に データが書き込まれてしまったことである. これはバッファオーバラン(buffer overun)と呼ばれ, C言語プログラムの(本来あってはならないが)よくある重大な欠陥である.


Step 3. 安全対策

バッファオーバランを防止してみよう:

...
		printf("要素番号(-1 で終了)> ");
		scanf("%d", &i);
		if (i < 0) break;
		if (i >= 3) continue;	// 範囲外の要素番号を無視
...

これで,とりあえずオーバランは回避できただろう. しかし,次の通り,これだけでは不充分だ.

データ配列の要素数について,3 では少なすぎるので,増やしてみたい. この場合,ソースコード中の複数箇所にある定数 3 をすべて書き換える必要がある. しかし,この時,間違いが発生しやすい. たとえば,if (i >= 3) の 3 だけを 10 に書き換え, 配列宣言 data[3] の 3 をそのまま書き換え忘れてしまうと? バッファオーバランが簡単に発生してしまう.


Step 4. 更なる安全対策

プログラミング時の間違いの予防策を取り入れてみよう. 配列要素数をマクロ定義しておき, 即値(定数 3 そのまま)を ソースコード内に何度も書かないようにしておけばよい:

#include ...
#define N_DATA 3

int main(void)
{
	int	data[N_DATA];
	...
	while (1) {
		...
		if (i >= N_DATA) continue;
		...
		for (...; i < N_DATA; ...) {
			...
		...
	}
	...
}

こうしておけば,1箇所(#define の 3)だけ変更すれば, 関連するすべての部分を漏れなく自動的に書き換えてもらえる. 最初から,こんな書き方をしておくべきだったんだ.


練習問題

教科書 pp.113-114 の練習問題 7-1 および 7-4 に取り組め.


(c) 2018, yanagawa@kushiro-ct.ac.jp