несогласованное предупреждение: переменная может быть сбита с помощью longjmp или vfork

Я в основном убеждал себя, что столкнулся с какой-то ошибкой g ++ 4.8.3, но подумал, что сначала спрошу этот список, потому что у меня очень мало опыта работы с setjmp / longjmp. Я упростил свой код до следующего файла foo.cxx:

#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. Компиляция также не выполняется с параметром -fPIC для g ++, но это обязательный аргумент в моей среде.

Чтобы увидеть ошибку компиляции:

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:

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

Может кто-нибудь объяснить мне, почему val может быть заторможено, если это структура, но не если это int? Есть ли какие-либо идеи о других способах успешной компиляции с помощью структуры, как указано в комментариях в коде? Или это указывает на ошибку компилятора?

Приветствуются любые идеи и комментарии.


person boulderpika    schedule 06.01.2015    source источник
comment
затирание setjmp и т. д., вероятно, связано с нахождением в реестре.   -  person Basile Starynkevitch    schedule 06.01.2015
comment
Открытая ошибка, которая может быть этой gcc.gnu.org/bugzilla/show_bug.cgi ? id = 48968   -  person Richard Critten    schedule 06.01.2015
comment
Что касается комментария Базиля, что будет, если снизить уровень оптимизации? Вы проверили, какой ассемблерный (или даже промежуточный) код генерирует компилятор? Это может дать вам представление о том, что происходит.   -  person Some programmer dude    schedule 06.01.2015
comment
Компиляция не выполняется ни на одном уровне оптимизации. Отключите оптимизацию, и компилятор будет доволен. Ни одну сборку пока не смотрел.   -  person boulderpika    schedule 06.01.2015
comment
Я не могу воспроизвести это с помощью GCC 4.9.1, поэтому это может быть исправлено (или исправлено) там. Это не относится к вашему примеру, и вы, вероятно, уже знаете, но только для записи: выход из области, которая требует нетривиального раскручивания стека через longjmp, вызывает неопределенное поведение в C ++.   -  person 5gon12eder    schedule 06.01.2015


Ответы (2)


setjmp() сохраняет текущий стек. Поскольку она вызывается перед объявлением val, этой переменной не будет в сохраненном стеке.

После setjmp() переменная инициализируется, и если код позже переходит обратно к точке setjmp, переменная будет инициализирована снова, затирая старую переменную. Если в старом экземпляре должен быть вызван нетривиальный деструктор, это поведение undefined (§18.10 / 4):

Пара вызовов _5 _ / _ 6_ имеет неопределенное поведение, если замена setjmp и longjmp на catch и throw вызовет любые нетривиальные деструкторы для любых автоматических объектов.

Скорее всего, деструктор старого экземпляра не будет вызван. Я предполагаю, что gcc не предупреждает о примитивных типах, поскольку у них нет деструкторов, но предупреждает о более сложных типах, где это может быть проблематично.

person sth    schedule 06.01.2015
comment
Но OP MyStruct является типом POD, поэтому инициализация не выполняется. Сразу после выполнения строки MyStruct val;, val.a и val.b определены не более, чем после возврата к ней после longjmp. - person 5gon12eder; 07.01.2015
comment
@ 5gon12eder: Верно, но вы должны учитывать, насколько сложно компилятору определить, обосновано ли предупреждение. - person MSalters; 07.01.2015

Здесь действуют несколько факторов:

  1. struct вместо int
  2. не использую memset (признаю, я не понимаю, как это может усугубить ситуацию)
  3. повторение цикла более двух раз - если вы выполняете итерацию дважды, компилятор разворачивает цикл
  4. параметр командной строки -fPIC (производит независимый от позиции код)

Только при наличии всех четырех факторов компилятор выдает предупреждение. Похоже, они представляют собой идеальный шторм для оптимизатора, у которого есть своего рода нервный срыв (см. Ниже). Если какой-либо из этих факторов отсутствует, компилятор просто оптимизирует все до нуля, поэтому он может просто игнорировать setjmp.

Остается открытым вопрос о том, является ли это ошибкой - код, по-видимому, все еще работает (хотя я его не тестировал). Но в любом случае проблема, похоже, исправлена ​​в версии 4.9, поэтому очевидным решением является обновление.

Вот машинный код (NSFW):

SomeFunc(MyStruct):
    rep; ret
main:
    pushq   %r12
    pushq   %rbp
    pushq   %rbx
    subq    $224, %rsp
    leaq    16(%rsp), %rdi
    call    _setjmp@PLT
    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
person TonyK    schedule 06.01.2015