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の利用には細心の注意が必要です。