SetJmp / LongJmp: Почему возникает ошибка сегментации?

Следующий код резюмирует проблему, с которой я столкнулся на данный момент. Мой текущий поток выполнения выглядит следующим образом: я работаю в GCC 4.3.

jmp_buf a_buf;
jmp_buf b_buf;

void b_helper()
{
    printf("entering b_helper");
    if(setjmp(b_buf) == 0)
    {
        printf("longjmping to a_buf");
        longjmp(a_buf, 1);
    }
    printf("returning from b_helper");
    return; //segfaults right here
}
void b()
{
    b_helper();
}
void a()
{
    printf("setjmping a_buf");
    if(setjmp(a_buf) == 0)
    {
        printf("calling b");
        b();
    }
    printf("longjmping to b_buf");
    longjmp(b_buf, 1);
}
int main()
{
    a();
}

Вышеупомянутый поток выполнения создает segfault сразу после возврата в b_helper. Это похоже на то, как если бы действителен только фрейм стека b_helper, а стеки под ним стираются.

Кто-нибудь может объяснить, почему это происходит? Я предполагаю, что это оптимизация GCC, которая стирает неиспользуемые кадры стека или что-то в этом роде.

Спасибо.


person jameszhao00    schedule 04.09.2009    source источник


Ответы (2)


Вы можете только longjmp() вернуть вверх стек вызовов. Вызов longjmp(b_buf, 1) - это то место, где что-то начинает идти не так, потому что фрейм стека, на который ссылается b_buf, больше не существует после longjmp(a_buf).

Из документации для longjmp:

Подпрограммы longjmp () не могут быть вызваны после возврата подпрограммы, которая вызвала подпрограммы setjmp ().

Это включает "возврат" через longjmp() из функции.

person Greg Hewgill    schedule 04.09.2009
comment
Есть ли способ longjmp вниз по стеку? Можно ли скопировать стек из b в b_helper в кучу и выполнить оттуда? Кроме того, почему кадр стека, на который ссылается b_buf, больше не действителен после прыжка вверх по стеку вызовов? - person jameszhao00; 05.09.2009
comment
Как только часть стека освобождена, она полностью недействительна (вызовы других функций, прерывания или что-то еще может перезаписать память). - person Michael Burr; 05.09.2009
comment
Вы можете думать о longjmp() как о расширенном доходе. Успешный longjmp() работает как серия последовательных возвратов, раскручивая стек вызовов до тех пор, пока он не достигнет соответствующего setjmp(). После того, как кадры стека вызовов размотаны, они больше не действительны. Это контрастирует с реализациями сопрограмм (например, Modula-2) или продолжений (например, Scheme), где стек вызовов остается действительным после перехода в другое место. C и C ++ поддерживают только один линейный стек вызовов, если вы не используете потоки, в которых вы создаете несколько независимых стеков вызовов. - person Greg Hewgill; 05.09.2009
comment
@ jameszhao00 - Я думаю, что для того, чтобы иметь хоть какую-то надежду сделать что-то вроде того, что вам кажется, потребуется погрузиться в ассемблерный код. Другой вопрос, хорошая ли это идея. - person Michael Burr; 05.09.2009
comment
Ах. Итак, в основном я могу сделать GCC принудительно встроить b_helper () и в конце b_helper () longjmp вернуться к a? - person jameszhao00; 05.09.2009
comment
@ jameszhao00: но если вы хотите создавать сопрограммы на C, взгляните на chiark.greenend.org.uk/~sgtatham/coroutines.html, в котором описано использование переменных состояния и операторов переключения для получения аналогичного эффекта. - person Michael Burr; 05.09.2009
comment
Кроме того, я не понимаю, почему после того, как я longjmp вернулся к a из b_helper (), фрейм b_helper () все еще действителен. По документам его следовало раскрутить. - person jameszhao00; 05.09.2009
comment
Дело в том, что кадр b_helper () недействителен, а b_buf относится к недопустимому кадру. То, что исполнение продолжается до конца b_helper(), является случайностью. - person Greg Hewgill; 05.09.2009

В стандарте говорится о longjmp() (7.13.2.1 Функция longjmp):

Функция longjmp восстанавливает среду, сохраненную при последнем вызове макроса setjmp в том же вызове программы с соответствующим аргументом jmp_buf. Если такого вызова не было, или если функция, содержащая вызов макроса setjmp, завершила выполнение в промежуточный период

со сноской, которая немного поясняет:

Например, при выполнении оператора return или из-за того, что другой вызов longjmp вызвал переход к вызову setjmp в функции ранее в наборе вложенных вызовов.

Таким образом, вы не можете longjmp() назад и вперед по вложенным _3 _ / _ 4_ наборам.

person Michael Burr    schedule 04.09.2009
comment
Если в функции есть несколько вложенных областей видимости, а setjmp () выполняется внутри вложенной области, может ли кто-то законно вернуться туда longjmp (), если кто-то покинул область действия с помощью setjmp (), но остался в той же функции? Означает ли это, что изменчивые переменные во вложенной области должны хранить свои значения, если выполнение покидает эту область и снова входит в нее (но остается в функции)? - person supercat; 03.11.2011