■この記事の概要
この記事では、C言語で無限ループが発生する6つの典型的な原因を解説しています。ループカウンタの更新ミスや型の不一致、符号なし変数の使用、条件式の誤りなど具体例を挙げ、安全なコーディング方法と対策を詳述しています。特に初心者が陥りやすいミスに焦点を当て、効率的なデバッグ手法も紹介しています。
■無限ループとは
バグには色々あれど、
ゼロの割り算とこの無限ループは
バグがひとたびでると一斉総点検を命じられ
プロジェクト全員の仕事が一週間位止まります。
※あくまで筆者の経験談
■ループカウンタを更新しないと無限ループ
#include <stdio.h>
int main(void) {
for(int i = 0; i<10; i++){
for(int j = 0; j<10; i++){
printf("意図しない無限ループ[i=%d][j=%d]\r",i,j);
}
}
}
ループカウンタjの更新がありません。
文脈から以下の間違いでしょう。
間違い:for(int j = 0; j<10; i++){
正しい:for(int j = 0; j<10; j++){
※この問題はgccでは検出できず、clangで検出できました。
■ループカウンタにintより狭い型を使って無限ループ
#include <stdio.h>
typedef unsigned char U1;
int main(void){
//NG:U1型は256を表現できない
for(U1 i = 0 ; i < 256 ; i++){
printf("意図しない無限ループ %d\n",i);
}
}
U1型では255までしか表現できないので256になる事はなく
無限ループします。
この問題はメモリ節約のためと称して狭い型(char/short)を多用するプロジェクトでよく発生します。
ループカウンタには整数を扱うのに適した int 型を使用する事を推奨します。
■ループカウンタと最大値の型が違うと無限ループ
#include <stdio.h>
typedef unsigned char U1;
static void f(int max) {
for(U1 i = 0 ; i < max ; i++){
printf("意図しない無限ループ %d\n",i);
}
}
int main(void){
f(1024);
}
for文第2式の i < max において、
i は U1 型で255までしか表現できませんが
max は int 型で約21億まで表現できます。
※32bit CPUの場合
このコードの場合、
iを加算してもmaxに到達する事はありません。
このコードの場合無限ループしますが、
残念ながら現状
gcc/clangで警告は何もでません。
■降順ループで0Uと比較して無限ループ
#include <stdio.h>
#define MAX 10U
#define MIN 0U
int main(void){
for(int i = MAX ;i >= MIN; i--){
printf("無限ループ %d\r",i);
}
}
int i は符号付き変数ですが、
i >= MIN の比較において
マクロ MIN は0Uであり、
接尾子Uが付いています。
このため
i>=0Uの両辺は
符号無しとして扱われるため
条件は常に成立します。
よって
このコードは無限ループします。
■降順ループで符号無し変数を使って無限ループ
#include <stdio.h>
#include <string.h>
int main(void){
int ary[] = {1,2,3,4,5,6,7};
size_t num = sizeof(ary)/sizeof(ary[0]);
for(size_t i = num - 1;i>=0;i--){
size_t j = i%num;
printf("意図しない無限ループ ary[%8zx]=%d\r",j,ary[j]);
}
}
符号無し変数(size_t i)は
負値にならないので
常に0以上です。
for文の第2条件(i>=0)が常に成り立つので
このコードは無限ループします。
符号無し変数以外のループカウンタを
使用を禁止にしているプロジェクトで
この問題はよく発生するので、
ルールの見直しを推奨します。
■ループの条件式でノットイコール(!=)を使ってはいけない
#include <stdio.h>
int main(void){
for(int i = 1;i < 10 ;i+=2){
printf("〇最大値を跨がない %d\n",i);
}
for(int i = 1;i != 10 ;i+=2){
printf("✖最大値を跨いで無限ループ %d\n",i);
}
}