2015-01-06 17 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:

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

可有人請向我解釋爲什麼,如果它是一個VAL可能會被打一頓結構,但不是如果它是一個int?正如代碼中的註釋所表明的,對於使用該結構進行編譯的其他方法的任何見解?或者這是指向一個編譯器錯誤?

任何見解和意見,非常感謝。

+0

重挫通過'setjmp'等可能與在寄存器之中。 –

+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而不是int
  2. 沒有使用memset(我承認我不明白這是如何使事情變得更糟)
  3. 迭代循環更比兩次 - 如果你只迭代兩次,編譯器展開循環
  4. -fPIC命令行選項(這產生位置無關的代碼)

只有當所有這四個因素都存在時,編譯器纔會產生警告。它似乎構成了優化器的完美風暴,它有一種神經衰弱(見下文)。如果這些因素中的任何一個都不存在,編譯器會將所有內容優化爲無,因此它可以忽略setjmp

這是否是一個bug值得商榷 - 代碼大概仍然有效(儘管我沒有測試過它)。但無論如何,這個問題在4.9版本中似乎已經得到修復,所以顯而易見的解決方案就是升級。

這裏是機器代碼(NSFW):

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