【C言語】
ゼロ除算エラーとその対処法 (Divide By Zero)

ゼロ除算検出

warning: division by zero

警告:ゼロ除算
[-Wdiv-by-zero]

■ゼロ除算を見つけるのは難しい

コンパイラの警告や静的解析ツールを使用しても
ゼロ除算を見つけるのは難しい。

人海戦術で単純検索してもうまくいかない。

そんな時は逆アセンブルしてゼロ除算を検索すると
うまくいくかもしれない。

■ゼロ除算問題の簡単な例

#include    <stdio.h>
int main(void)
{
    int x = 0 ; 
    int y = 3 ;
    printf("%d\n",y/x) ;    //検出できない
    printf("%f\n",y/0.0) ;  //意図的かもしれない
    printf("%d\n",y/0) ;    //検出できる
}

ツールでは ゼロ除算 問題を検出できない場合がほとんどです。
gccでは
8行目のように分母が定数0の時しか検出できません。
6行目のように分母が変数の時は検出できません。


■ゼロ除算問題の見つけ方

ゼロ除算問題が発生してしまった時、
同様の問題がないか確認するために
割り算を行っている箇所をソースコードから
見つけ出す必要があります。


しかし ”/” や”%”を以下のように単純検索してもうまくいきません。

 $grep  ”/”  *.c

⇒コメントの”//” や ”/*” ばかり膨大な量が検索されます。


こんな時はCソースから割り算を検索するのではなくて
一度逆アセンブルして”div” 命令などを検索したほうが
早いかもしれません。

■”div”命令を検索するSHELL

gcc -c -g div.c -O2
objdump div.o --disassemble --line-numbers --source > TMP
grep div TMP

■ガード処理を抜けるゼロ除算の失敗例

int f(int x,double d)
{
    if(d) {//ゼロ除算ガード
        printf("THEN %f\n",x/d);
        return  x/(int)d;
    } else {
        printf("ELSE\n");
        return  0;
    }
}
int main(void)
{
    f(2,0.14142);
}

除算命令を見つけたらガード処理があるか
「人力」で確認する必要があります。
その際上記コードのように見かけ上
ゼロ除算のガード処理があっても
すり抜ける場合があるので注意して下さい。
3行目の判定では浮動小数点変数 d はゼロではないので then 節へ行きますが、
5行目の(int)dで丸めたためゼロになってしまいます。

■ゼロ除算後の不適切なガード処理

(warning): Either the condition ‘y==0’ is redundant or there is division by zero at line 3.

警告:ゼロ除算後のガード処理は無駄

[(warning)zerodivcond]

#include    <stdio.h>
int 割り算(int 分子,int 分母) 
{
    int 結果 = 分子/分母;
    if(分母 == 0){//ガード処理
        return  0;
    }
    return  結果;
}
int main(void) {
    printf("%d\n",割り算(3,0)) ;
}

分子/分母を実行する前に
if(分母 == 0)でガードしてください。


浮動小数点例外発生例

//全員0点の時
#include <stdio.h>
int main(void){
    int     点数[]= {
          100,0,75,25
    };
    
    const int max = sizeof(点数)/sizeof(点数[0]);
    
    int     合計=0;
    for(int i = 0 ; i < max;i++){
        合計 += 点数[i];    
    }
    printf("平均点=%d\n",合計/max) ;
}

1行だけ変更して
浮動小数点例外を発生させて下さい。

➡解答

    int     点数[]= {
     //     100,0,75,25
    };

配列の初期値を全部コメントにすると
配列の要素数(max)が0になります。

Visual Studioだとコンパイルエラーになりますが、
gccではコンパイル可能で実行すると
浮動小数点例外が発生します。


参考:

CWE-369: Divide By Zero

INT33-C. 除算および剰余演算がゼロ除算エラーを引き起こさないことを保証する

https://www.jpcert.or.jp/sc-rules/c-int33-c.html