連載
» 2021年02月18日 10時00分 公開

ソフトウェア技術者のためのバグ百科事典(17)無限ループやbreak抜け、コーディングのバグ再び山浦恒央の“くみこみ”な話(138)(1/3 ページ)

ソフトウェア技術者に向けて、バグに関する基礎知識をまとめていく新シリーズ「バグ百科事典」。第17回は、第14回でテーマにしたコーディングのバグを再び取り上げます。無限ループやbreak抜けなどまだまだあります!

[山浦恒央 東海大学 大学院 組込み技術研究科 非常勤講師(工学博士),MONOist]

1.はじめに

 バグ百科事典では、筆者が気になったバグを紹介し、読者の皆さまに「バグの早期発見」と「バグの未然防止」に役立てていただくものです。

 少しずつ、バグの知識が深まってきたでしょうか。今回は、誰もが経験するコーディングのバグを取り上げます。

⇒連載「山浦恒央の“くみこみ”な話」バックナンバー

2.コーディングフェーズのバグ

ループ ※写真はイメージです

 ソフトウェア開発業務で最も楽しい作業が、コーディングフェーズでしょう。一般の人々は、プログラム開発=コーディングとの認識があるようですが、ソフトウェア開発の一つのフェーズにすぎません。このフェーズでは、仕様や設計を基にしてプログラムを記述します。

 楽しい作業の中にも苦しみは存在します。特に、コンピュータは、人間が指示した通りにしか動作しませんので、自分の思い通りのプログラムを書くことは簡単ではありません。例えば、ループ処理を考えてみましょう。人間であれば、何回も同じ作業を続けていると、「もうやりたくない」「もう十分作業したよね」と思いますが、コンピュータは違います。コンピュータは、とにかく言われた通りに、電源がOFFになるまで作業を続けるのです。

 コーディングフェーズのバグは、連載第135回「ソフトウェア技術者のためのバグ百科事典(14)地獄の作業と化すコーディングのバグ」で取り上げ、その際、下記のバグを紹介しました。

  • 変数の未初期化
  • 似た名前の変数を間違える
  • 関数の使用方法を間違える
  • 意図しないキャストが発生する

 上記は、常連のコーディングのバグです。一見、単純なバグと思いますが、プログラムを結合して、システム全体の挙動を見ると、非常に不可解な動作をします。よって、この単純バグにも目を向けることは大切です。

 今回は、上記以外のコーディングのバグについて、「よくあるコーディングのバグ(その2)」として紹介します。なお、説明はC言語をベースにしています。

3.よくあるコーディングのバグ(その2)

 よくあるコーディングのバグを以下に示します。

3.1 無限ループ

 プログラムを書くメリットは、同じ処理を繰り返し実行できることです。これをプログラミングでは、ループ処理と言いますね。ループ処理の典型的なバグの一つが、「無限ループ」です。例えば、「ループ内でbreakを書き忘れる」「条件分岐の関係上、ループから抜け出せない」などがあります(リスト1)。

//無限ループ
	while (1){
		//ここから抜けられない
		//処理
}
リスト1 無限ループの例(一部抜粋)

 リスト1は、無限ループの例です。無限ループで注意することは、ループを抜ける処理の記述を忘れないことです。この処理が抜けていると、強制終了しない限り、プログラムは終了しません。

3.2 switch文のbreak抜け

 プログラムの流れを制御するには、条件分岐が最適です。条件分岐を行う構文の一つが、switch文です。switch文では、switchとcaseをセットで書き、条件に対応した処理を記述します。多くの条件を順々に書けるのがメリットですが、ループを抜けるbreak文を書き忘れると、意図した動作をしません(リスト2)。

#include <stdio.h>
int main(){
	int a = 1;
	switch (a) {
	case 1:
		printf("case 1:を実行する");
		//break文が抜けている
	case 2:
		printf("case 2:を実行する");
		break;
	}
	return 0;
}
リスト2 break抜けの例(一部抜粋)

 リスト2は、switch文の例です。switch文では、入力した値に合致する条件文を実行します。この場合、a = 1の場合は、「case 1:」、a = 2の場合は、「case 2:」を実行しますね※1)

 今回の例では、「case 1:」の処理にbreak文が入っていません。break文とはループから抜ける処理ですから、この例題では「case 1:」「case 2:」のどちらも実行することになります。

※1)defaultは、説明の便宜上、省略しています。ちなみに、defaultの書き忘れも単純バグの一つでしょう。

3.3 代入の記述ミス

 プログラムを記述するのは人間ですから、記述ミスが起きます。誰しも経験する代表例の一つが、「if(a == 1)」を「if(a = 1)」と書くことですが、他にもリスト3のような例があります。

#include <stdio.h>
int main() {
	int a = 0;
	
	a == 1;
	if (a == 1) {
		printf("この行を実行しない\n");
	}
	return 0;
}
リスト3 記述ミスの例

 これは、変数aに1を代入し、if以下を実行するプログラムです。一見、よさそうに見えますが、「a = 1;」と書くべきところに、「a == 1;」と書いてしまっています。この場合、変数aは0のままで、if文以下を実行できません。

3.4 領域外を書き換える

 配列を扱うプログラムでやりがちなのは、宣言した配列の領域外にアクセスしたり、その中身を書き換えたりすることです(リスト4)。

#include <stdio.h>
int main(){
	int i;
	int data[5];
	for (i = 0; i <= 5; ++i) {
		data[i] = i;
	}
	return 0;
}
リスト4 領域外書き換えの例題

 リスト4では、配列data[5]と宣言して、ループ内で、変数iの値を代入しています。配列は、0から数えますので、使えるのはdata[4]までです。例題では、ループ変数iを0〜5まで繰り返しており、宣言していない変数data[5]に値を代入していますね。結果として、領域外を書き換えてしまいます。

 この例では不可解な事象は起きませんが、領域外を書き換えると、プログラムの停止や、意図しない動作を引き起こします。注意してください。

       1|2|3 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.