【C言語】
Segmentation faultを今すぐ直したい時の解析方法

Segmentation fault

静的解析で敵を見つける

●gcc -Wall -Wextra -Oでコンパイルして警告を直す

clang -Wall -Wextra -Oでもコンパイルして
警告を直しましょう。

-Oオプションで最適化有りと無しで警告が出たり、出なかったりします。
ざっくり最適化有りのほうが有用警告が沢山出るようです。
例えば未初期化変数の警告は
最適化無しだとあまり出ませんが、
最適化ありだと警告してくれます。
でも、未使用static関数が最適化で消えてしまうと、、、、、


gcc -fanalyzer -O2でコンパイルして警告を直す

clang –analyze -O2でもコンパイルして
警告を直しましょう。


●clang-tidyやcppcheckも使ってみる

使い方はこちら。

https://bugs-c.info/uncategorized/3248/

ここまでやって問題解決できない場合
静的解析はあきらめましょう。


動的解析で敵を見つける

●gccの場合
gcc -fsanitize=address -g でコンパイルして実行してください。

●clangの場合
clang -fsanitize=address -g でコンパイルして実行してください。

以下のようにエラー個所(ファイル名行番号)を教えてくれます。

-gオプションを付けないと行番号までは教えてくれません。
-Oオプション(最適化)の有無でエラーが出たり出なかったりする時があります。

前略
SUMMARY: AddressSanitizer: heap-buffer-overflow /mnt/中略/x.c:10 in main
後略

敵 segmentation faultはこんな時出ます

出ない時もあります。
再現性が無いので苦労します。

➡未初期化ポインタを間接参照しない事

#include <stdio.h>
#include <stdlib.h>
int main(void){
    int x ;
    int *p ;    //未初期化ポインタ
    x   = 1234 ;
    *p  = 5678 ;

    char  *str; //未初期化ポインタ
    printf("数字を入れてね");
    scanf("%s",str);
    printf("%d\n",atoi(str));
}

ポインタのpとstrは初期化されていないのでメモリのどこを指しているか分かりません。メモリの何処かを破壊してしまいます。

➡NULLポインタを間接参照しない事

#include <stdio.h>
int main(void){
    char    x  = '\0';
    char    *p = NULL;
    x   = 'A' ;
    *p  = 'B' ;//NULLポインタ間接参照
    printf("%c:%c\n",x,*p);
}

NULLポインタの指す先を参照すると未定義動作となります。

➡配列の添字が大きすぎる

#include <stdio.h>
int main(void){
    int     buf[10] = {1,2,3,4,5,6,7,8,9,10};
    //buf[10]で宣言したらbuf[9]までしか使えない
    printf("%d\n",buf[10]);
}

buf[10]で宣言したらbuf[9]までしか使えません。

➡配列の添字が大きすぎる(for文回り過ぎ)

#include <stdio.h>
int main(void){
    int     buf[10];
    
    //  int i = 0; i < 10;i++){ が正解   
    for(int i = 1; i <= 10;i++){
        buf[i] = i;
    }
    printf("%d\n",buf[0]);
}

これはOff-by-oneエラーと言うバグです。
<=と<を間違えて配列の境界外を参照してしまうバグです。

➡自動変数配列のサイズが大きすぎる

#include <stdio.h>
int main(void){
    //自動変数配列のサイズが多き過ぎる
    char    buf[0x7FFFffff] = {0};
    printf("%d\n",buf[0]);
}

配列が大きすぎます。
自動変数ではなくて外部変数にするとうまくいく場合もあります。

➡fclose(NULL)を実行しちゃ駄目

#include <stdio.h>
int main(void){
    FILE *fp = fopen("存在しなファイル","re");
    if(fp == NULL){
        perror(NULL);
        fclose(fp);
    }
}

➡strcpy()のバッファサイズをケチらない事

#include <stdio.h>
#include <string.h>

int main(void){ 
    char    filename[6];
    strcpy(filename,"jugemu");//終端文字の考慮が無い
    puts(filename);
    fflush(NULL);
    strcpy(filename,"/etc/jugemu/jugemu/Goko/no/Surikire");//第二引数長すぎ
    puts(filename);
    fflush(NULL);
}

バッファサイズに余裕を持たせましょう。

➡fgets()のバッファが小さすぎ(ケチらない事)

#include <stdio.h>
#include <stdlib.h>
int main(void){
    //終端文字の考慮がない。
    //ピッタリサイズでなくて余裕を持たせる。
    char    buf[4]; 
    printf("4桁の数字を入れて下さい");
    fgets(buf,BUFSIZ,stdin);
    printf("%d\n",atoi(buf));
}

ピッタリサイズでなく余裕を持たせましょう。

➡malloc()で確保したサイズが小さい(型違い)

#include <stdio.h>
#include <stdlib.h>
int main(void){
    //sizeof(int)はsizeof(double)の間違い
    double *p = malloc(sizeof(int));
    if(p == NULL){
        perror(NULL);
        return 1;
    }
    *p = 3.14 ;
    printf("%lf\n",*p);
    free(p);
}

intとdoubleでは大きさが違うのでmallocで確保した領域外を破壊します。

➡文字列リテラルに書いちゃ駄目

#include <stdio.h>
int main(void){
    char    *p = "string" ;
    *p = 'X' ;
}

文字列リテラルの領域は書き込み禁止(の場合が多い)です。

 

参考:

ARR30-C. 境界外を指すポインタや配列添字を生成したり使用したりしない