setjmp/longjmpによるメモリリークパターンを紹介します。
次のコードを見てください。
main.c
#include <stdio.h> #include <stdlib.h> #include <setjmp.h> jmp_buf env; void sub(void) { longjmp(env, 1); } int main(int argc, char** argv) { char* buf1 = NULL; char* buf2 = NULL; buf1 = (char*)malloc(100); printf("1:buf1 = %p\n", buf1); if(setjmp(env)){ printf("3:buf1 = %p\n", buf1); printf("4:buf2 = %p\n", buf2); free(buf1); free(buf2); return 0; } buf2 = (char*)malloc(200); printf("2:buf2 = %p\n", buf2); sub(); return 0; }
開始直後に確保したメモリをbuf1に、setjmp後に確保したメモリをbuf2に結び付けています。
その後sub()呼び出しによりlongjmpが行われ、遷移先(setjmp戻り真の場合の処理)で各メモリを解放する、というものです。
このコードをUbuntu Linux (x86)上でコンパイルし、実行すると
$ gcc main.c -O0 $ ./a.out 1:buf1 = 0x83a9008 2:buf2 = 0x83a9070 3:buf1 = 0x83a9008 4:buf2 = 0x83a9070 $
となります。特に問題なく、メモリ解放できていそうです。
しかし、これを最適化ありでコンパイルすると、
$ gcc main.c -O2 $ ./a.out 1:buf1 = 0x8170008 2:buf2 = 0x8170070 3:buf1 = 0x8170008 4:buf2 = (nil) $
となります。
buf2がNULLになっているため、buf2の解放ができていません。
理由は、最適化されていない場合、buf1、buf2はスタックポインタ(レジスタ)が"指し示す"スタック上の1データにすぎないため、setjmpコール時点もlongjm後も変化しない。しかし、最適化が行われると、このコードの場合、buf1やbuf2そのものがレジスタに割り当てられようです。setjmpでコール時点でのそのレジスタ状態(buf2はNULLだという状態)が保持され、longjmpで遷移した時にsetjmpで保持されたもの(buf2はNULLだという状態)がレジスタとして復元されるためです。
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/longjmp.3.html
に、
以下の条件が全て成立する場合、 longjmp() の呼び出しが行われた後の自動変数の値は未定義 (unspecified) となる。
- その自動変数が、対応する setjmp(3) 呼び出しを行った関数のローカル変数である。
- 自動変数の値が setjmp(3) と longjmp() の間で変更されている。
- volatile として宣言されていない。
とあります。
また、
-
longjmp() や siglongjmp() を使うと、プログラムは理解しづらく、保守しにくいものになる。別の方法が可能なら、それを使うべきである。
また、setjmpのほうにも
-
setjmp() や sigsetjmp() を使うと、プログラムは理解しづらく、保守しにくいものになる。別の方法が可能なら、それを使うべきである。
と記載があります。
setjmp/longjmpの利用には細心の注意が必要です。