2015-01-06 40 views
7

g ++ 4.8.3のバグがいくつか発生したと私は確信していますが、 setjmp/longjmp。私は、次のfoo.cxxに問題の私のコードを単純化しています一貫性のない警告:変数が 'longjmp'または 'vfork'で詰まっている可能性があります

#include <setjmp.h> 
#include <string.h> 

// Changing MyStruct to be just a single int makes the compiler happy. 
struct MyStruct 
{ 
    int a; 
    int b; 
}; 

// Setting MyType to int makes the compiler happy. 
#ifdef USE_STRUCT 
typedef MyStruct MyType; 
#elif USE_INT 
typedef int MyType; 
#endif 

void SomeFunc(MyType val) 
{ 
} 

static void static_func(MyType val) 
{ 
    SomeFunc(val); 
} 

int main(int argc, char **argv) 
{ 
    jmp_buf env; 
    if (setjmp(env)) 
    { 
     return 1; 
    } 

    MyType val; 
#ifdef USE_STRUCT 
    val.a = val.b = 0; 
#elif USE_INT 
    val = 0; 
#endif 
    // Enabling the below memset call makes the compiler happy. 
    //memset(&val, 0, sizeof(val)); 

    // Iterating 1 or 2 times makes the compiler happy. 
    for (unsigned i = 0; i < 3; i++) 
    { 
     // calling SomeFunc() directly makes the compiler happy. 
     static_func(val); 
    } 
    return 0; 
} 

私はこのコードをコンパイルするG ++ 4.8.3を使用します。興味深いのは、USE_STRUCTを定義すると、コンパイルは失敗しますが、USE_INTで成功するということです。コードには、USE_STRUCTを使用してコンパイルを成功させる方法を示すコメントがあります。コンパイルはg ++の-fPICオプションでも失敗しますが、これは私の環境で必須の引数です。コンパイル・エラー表示するには

g++ -DUSE_STRUCT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx 
foo.cxx: In function ‘int main(int, char**)’: 
foo.cxx:26:5: error: variable ‘val’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] 

しかし、単純なint型を使用してもOKです。それは、Aの場合、valは上書きされるかもしれない理由

g++ -DUSE_INT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx 

誰かが私に説明していただけます構造体ですが、int型ではないのですか?コード内のコメントに示されているように、構造体を使ってコンパイルを成功させる他の方法についての洞察はありますか?あるいはこれはコンパイラのバグを指していますか?

洞察とコメントは大変ありがとうございます。

+0

つかうおそらくレジスタであることに関連している:ここ

はマシンコード(NSFW)です。 –

+1

このバグがあります。https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48968 –

+1

Basileによるコメントに関して、最適化レベルを下げるとどうなりますか?コンパイラが生成するアセンブリコード(または中間コード)を確認しましたか?それはあなたに何が起こっているのかのヒントを与えるかもしれません。 –

答えて

3

setjmp()は、現在のスタックを保存します。 valの宣言の前に呼び出されているため、その変数は保存されたスタックには含まれません。

setjmp()の後に変数が初期化され、コードが後でsetjmpポイントに戻った場合、変数は再び初期化され、古い変数が破損します。古いインスタンスで呼び出されるべき非自明なデストラクタが存在することになるならば、これは未定義の動作(§18.10/ 4)である:

A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects.

多分古いインスタンスのデストラクタが呼び出されません。私の推測では、gccはプリミティブ型については警告しません。なぜなら、デストラクタはないからですが、問題のあるより複雑な型を警告します。

+0

しかし、OPの「MyStruct」はPODタイプなので、初期化は行われません。 'MyStruct val;'、 'val.a'と' val.b'の行を実行した直後は 'longjmp'の後に戻ったときよりも定義されていません。 – 5gon12eder

+0

@ 5gon12eder:本当ですが、コンパイラが警告が正当であるかどうかを判断するのがどれほど難しいかを考慮する必要があります。 – MSalters

0

仕事でいくつかの要因がここにあります

  1. structの代わりに、ループ以上を反復memset(私はこれが事態を悪化させることができる方法を理解していない認める)
  2. を使用していないint
  3. コンパイラはループをアンロールします。
  4. -fPICコマンドラインオプション(これは位置に依存しないコードを生成します)

これらの要素の4つがすべて存在する場合のみ、コンパイラは警告を生成します。オプティマイザにとっては、彼らは一種の神経破壊(以下を参照)の完璧な嵐を構成しているようです。これらの要素のいずれかが存在しない場合、コンパイラはすべてを何もせずに最適化するだけなので、setjmpを無視できます。

これはバグかどうかは疑問です。おそらくコードはまだ動作しています(私はテストしていませんが)。しかし、どのような場合でも、問題はバージョン4.9で修正されているようですので、明らかな解決策はアップグレードすることです。 `setjmp`等によって

SomeFunc(MyStruct): 
    rep; ret 
main: 
    pushq %r12 
    pushq %rbp 
    pushq %rbx 
    subq $224, %rsp 
    leaq 16(%rsp), %rdi 
    call [email protected] 
    testl %eax, %eax 
    movl %eax, %ebp 
    jne .L5 
    movl $3, %ebx 
    movabsq $-4294967296, %r12 
.L4: 
    movq 8(%rsp), %rdx 
    andq %r12, %rdx 
    movl %edx, %eax 
    movq %rax, %rdi 
    movq %rax, 8(%rsp) 
    call SomeFunc(MyStruct)@PLT 
    subl $1, %ebx 
    jne .L4 
.L3: 
    addq $224, %rsp 
    movl %ebp, %eax 
    popq %rbx 
    popq %rbp 
    popq %r12 
    ret 
.L5: 
    movl $1, %ebp 
    jmp .L3 
関連する問題