Какова функция alloca() с setjmp?

Этот вопрос исходит из Практического использования setjmp и longjmp в C и Как реализовать сопрограмму в цикле for в c что я спросил.

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration

void routineA()
{
    int r = 0;

    printf("(A1)\n");

    if (setjmp(bufferA) == 0) {
        r++;
        alloca(2048);
        routineB();
    }

    printf("(A2) r=%d\n",r);

    if (setjmp(bufferA) == 0) {
        r++;
        longjmp(bufferB, 1);
    }

    printf("(A3) r=%d\n",r);

    if (setjmp(bufferA) == 0) {
        r++;
        longjmp(bufferB, 1);
    }

    printf("(A4) r=%d\n",r);
}

void routineB()
{
    int r = 0;

    printf("(B1)\n");

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B2) r=%d\n", r);

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B3) r=%d\n", r);

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B4) r=%d never reach\n", r);
}

int main()
{
    printf("main\n");
    routineA();
    return 0;
}

Я изучаю реализацию сопрограммы C. и пытаюсь увидеть, что произошло в стеке после longjmp.

Вопрос 1:

Что за волшебство делает стек routineB живым после использования alloca(2048)? Я слышал, что alloca это зло, но почему он делает стек расширенным. Должен ли я использовать это так?

Выход:

main
(A1)
(B1)
(A2) r=1
(B2) r=1
(A3) r=2
(B3) r=2
(A4) r=3

Вопрос 2:

После удаления alloca(2048). это дает другой результат после того, как компилятор отключает оптимизацию (-O2).

-O0

main
(A1)
(B1)
(A2) r=1
(B2) r=6356584
(A3) r=2
(B3) r=6356584
(A4) r=3

-O2

main
(A1)
(B1)
(A2) r=1
(B2) r=0
(A3) r=1
(B3) r=0
(A4) r=1

если это не неопределенно, как заставить код вести себя так же? если это так, пожалуйста, забудьте Q2.


person JustWe    schedule 23.05.2018    source источник
comment
Остерегайтесь неопределенного поведения   -  person Basile Starynkevitch    schedule 23.05.2018
comment
Как я объяснил здесь, код вызывает неопределенное поведение. Вы не должны делать такие скачки между функциями.   -  person Ajay Brahmakshatriya    schedule 23.05.2018
comment
@AjayBrahmakshatriya Так что я просто буду рассматривать longjmp как особый return.   -  person JustWe    schedule 23.05.2018
comment
@Jiu, longjmp не всегда возврат. Он завершает функцию только в том случае, если соответствующий setjmp был в функции ранее в наборе вложенных вызовов. Если setjmp находится в той же функции, это похоже на обычный goto. Также setjmp может быть на много ступеней ниже вложенности. Так что это совсем не похоже на return.   -  person Ajay Brahmakshatriya    schedule 23.05.2018
comment
@AjayBrahmakshatriya Спасибо! Лучший обзор использования longjmp и setjmp.   -  person JustWe    schedule 23.05.2018
comment
alloca не такой злой, как setjmp/longjmp.   -  person Lundin    schedule 23.05.2018


Ответы (2)


Вот статья о реализации coros с помощью setjmp/longjmp/alloca: https://fanf.livejournal.com/105413.html .

Идея состоит в том, что для того, чтобы B сохранил свой полный контекст (не только регистры (сохраняемые setjmp), но и локальные переменные в стеке) при переходе обратно к A, B нужен свой собственный стек или, по крайней мере, он должен убедитесь, что все, что делает A, не перезапишет переменные B.

alloca — это способ добиться этого, не углубляясь в сборку. alloca в основном переместит B намного дальше в стеке, чем A, так что, если A не использует глубокую рекурсию или что-то еще, что заставит его использовать более 2 КБ (в данном случае) своего стека, A и B сохранят свои локальные в стеке переменные отдельно.

(Этот метод совершенно естественно не соответствует строго C, и было бы еще меньше, если бы вы использовали переходы вперед и назад между несколькими стеками malloc.)

person PSkocik    schedule 23.05.2018
comment
Таким образом, разделенный стек 2048 делает A и B отдельно? - person JustWe; 23.05.2018
comment
@Jiu alloca(2048) перемещает указатель стека вперед на 2048 байт, что означает, что подпрограмма B запустится на 2048 позже, и когда B longjmp-вернется к A, A сможет использовать 2048 байтов стека до того, как состояние B в стеке будет скомпрометировано. - person PSkocik; 23.05.2018

О решении второго вопроса:

Помещение int r в сегмент данных даст тот же результат как при выпуске, так и при отладке в GCC.

static int r = 0;
person JustWe    schedule 24.05.2018