warning: leak of FILE ‘fp’
[CWE-775]
警告: fopenしたらfcloseしないとメモリリークする
[-Wanalyzer-file-leak]
■この記事の概要
この記事では、C言語のfclose()
関数を忘れることによるメモリリークの問題と、その対処法について解説しています。
具体的なコード例を使って、fclose()
の多すぎる場合や少なすぎる場合のバグ、goto
文を使ったリソース解放の推奨方法が紹介されています。
■ファイルを閉じ忘れた事例
#include <stdio.h>
#include <string.h>
//gcc -fanalyzer
void DEBUG_log(char *str){
FILE *fp = fopen("DEBUG.log","a");
if(fp == NULL){
perror(NULL);
return ;
}
fprintf(fp,"%s",str);
//fclose(fp); 忘れた!
}
int main(void){
for(int i = 0; i< 2000;i++){
char log[256];
sprintf(log,"%s:%d id=%d\n",
__func__,__LINE__,i);
DEBUG_log(log);
}
}
単体試験の時は見かけ上問題もなく動作していたのが、
結合試験などで
何度もfopen()を実行して
fclose()していないので
リソース不足でシステム異常を起こし
問題が発覚する場合があります。
warning: double ‘fclose’ of FILE ‘fp’
warning: leak of FILE ‘fp’
警告: fclose多すぎ、少なすぎ
[-Wanalyzer-double-fclose]
[-Wanalyzer-file-leak]
■fcloseが多すぎるバグ例
//gcc -fsanitize=leak 動的解析
//gcc -fanalyzer 静的解析
#include <stdio.h>
int main(void) {
FILE *fp1 = fopen("/usr/include/stdio.h","r");
if(fp1 == NULL){
return 1;
}
FILE *fp2 = fopen("テスト用(存在しないファイル)","r");
if(fp2 == NULL){
//異常時処理
perror(NULL);
fflush(0);
fclose(fp1);
//処理続行
}
//正常時処理
//異常時と正常時の2回 fclose() してしまった例
fclose(fp1) ;
fclose(fp2) ;
}
同じファイルポインタを2回fclose() してしまった問題です。
異常時処理でfclose() しているのに正常処理でもfclose() してしまう場合が多いです。
この問題は以下のオプションで検出できます。
gcc -fsanitize=leak 動的解析
gcc -fanalyzer 静的解析
■fcloseが少なすぎるバグ例
#include <stdio.h>
int f(void){
FILE *fp1 = fopen("/usr/include/stdio.h","r");
if(fp1 == NULL) {
return 1;
}
FILE *fp2 = fopen("テスト用(存在しないファイル)","r");
if(fp2 == NULL){
return 1; //問題:fclose(fp1) 忘れ
}
// 処理色々~~~~~
// 後始末
fclose(fp1);
fclose(fp2);
return 0;
}
int main(void){
for(int i = 0;i < 1000;i++){
f();
}
}
■goto文を使ったfclose問題対策(推奨)
#include <stdio.h>
int main(void) {
FILE *fp1 = NULL ;
FILE *fp2 = NULL ;
fp1 = fopen("/usr/include/stdio.h","r");
if(fp1 == NULL){
goto err;
}
fp2 = fopen("テスト用(存在しないファイル)","r");
if(fp2 == NULL){
goto err;
}
//
//処理色々
//
err:
//クリーンナップ処理
if(fp1) fclose(fp1) ;
if(fp2) fclose(fp2) ;
}
関数末尾に資源解放の
クリーンナップ処理をまとめ、
そこへgoto で飛ぶ手法を推奨します。
注意事項:
fp1,fp2は関数の先頭でNULLで初期化する必要があります。
C99式に使用する直前で変数宣言すると
クリーンナップ処理で未初期化変数を参照する可能性があります。
参考:
■fclose(NULL)はSegmentation fault
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void NG_fclose(void){
char buf[BUFSIZ];
FILE *fp = fopen("/usr/include/stdio.h","r");
if(fp == NULL){
perror(NULL);
return ;
}
fgets(buf,BUFSIZ,fp);
fclose(fp);
fp = NULL; //間違い
fclose(fp);
}
int main(void){
NG_fclose();
}
fclose(NULL)は問題があります。
多分Segmentation faultします。
●free(NULL)は大丈夫
free(NULL)は問題ありません。
free(NULL)を実行しても何も起きません。