gcc-4.2 の関数ポインタキャストの取り扱い

これまで何の問題も無くコンパイル, 実行できていたプログラムが, 実行時に illegal instruction で落ちるようになってしまった. よく見るとコンパイル時にこんなWarningがでている.

test.c:10: warning: function called through a non-compatible type
test.c:10: note: if this code is reached, the program will abort

いろいろ調べてみたところ, こんなページ を見つけた. どうも, gccが関数ポインタの別のタイプへのキャストに対して厳しくなったらしく, タイプが違うと, 実行時に必ずアボートするようなコードを意図的に挿入しているらしい. アセンブラを出力させてみるとこんな関数を定義して呼び出している. .value 0x0b0f がillegalなinstructionになっているんだろう.

cast_direct:
	pushl	%ebp
	movl	%esp, %ebp
		.value	0x0b0f

それならコンパイル時にエラーではじいてくれればよさそうなものだが, 言語仕様的には, このようなコードに対しては実行時の挙動は未定義なものの, コンパイル時に関してはそうではないため, エラーにすることができかったらしい. なんとも不細工な話だ. 私がしらなかっただけで, このような制御はgcc-3.4から導入されていたらしい.

ただし,このチェックは完全ではなく, 上に挙げたページでは4.2でも通ってしまうコードを示している. 手元にあった gcc3.3, 3.4, 4.1, 4.2 で実際に動かしてみた.

               | 3.3        3.4        4.1       4.2
  -------------+---------------------------------------
  direct       |  o        x(segv)   x(ill)     x(ill)
  fptr         |  o        o         o          x(ill)
  assingment   |  o        o         o          o
  union        |  o        o         o          o

                        o: 実行可能, x: 実行不能 
                        segv - Segmentation Fault
                        ill  - illegal instruction   

4.2からはvoid * を介したキャストが許されなくなったことが分かる. しかし, これは余計なお世話というものではないだろうか. C言語は何でもできることが売りで, 「自分の足を撃つ」ことができてこそ. 結局回避できてしまうような中途半端なガードは不要だと思うのだが. 実行時にアボートするにしろ, もう少し原因が分かるようなメッセージを出してほしい. どうせ動作は未定義なんだから.

以下はテストのコード. 前掲のサイトからのコピペ.

typedef void (*void_fptr) (void);
int foo (void * a)  { return 0; }

int cast_direct(void)
{
    return (((int (*) (const void *))  foo) (0));
}
int cast_via_void_fptr(void)
{
    return (((int (*) (const void *)) ((void_fptr) foo)) (0));
}
int cast_via_assignment(void)
{
    int (*bar) (const void *) = (int (*) (const void *))foo;
    return bar (0);
}
int cast_via_union(void)
{
    union {
        int (*foo) (void *);
        int (*bar) (const void *);
    } u;
    u.foo = foo;
    return u.bar (0);
}