2009-09-04 10 views
6

Poniższy kod podsumowuje problem, który mam w tej chwili. Mój bieżący przepływ wykonania jest następujący i uruchomiony w GCC 4.3.SetJmp/LongJmp: Dlaczego wyrzucasz segfault?

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(); 
} 

Powyższy przepływ wykonania tworzy segfault tuż po powrocie w b_helper. To prawie tak, jakby tylko ramka stosu b_helper była ważna, a stosy pod nią są wymazywane.

Czy ktoś może wyjaśnić, dlaczego tak się dzieje? Zgaduję, że jest to optymalizacja GCC, która usuwa nieużywane ramki stosów lub coś takiego.

Dzięki.

Odpowiedz

12

Możesz tylko longjmp() wrócić w górę stos wywołań. Wywołanie do longjmp(b_buf, 1) jest tam, gdzie rzeczy zaczynają się nie udać, ponieważ ramka stosu, do której odwołuje się b_buf, już nie istnieje po longjmp(a_buf).

Z dokumentacji dla longjmp:

longjmp() procedury nie mogą być nazywane po rutynowych który nazwał setjmp() procedury zwrotu.

Obejmuje to "zwracanie" przez longjmp() poza funkcją.

+0

Czy istnieje sposób na zmniejszenie długości ramek stosu? Czy jest możliwe skopiowanie stosu z b do b_helper do sterty i wykonanie stamtąd? Ponadto, dlaczego ramka stosu, do której odwołuje się b_buf, nie jest już ważna po podskoku stosu wywołań? – jameszhao00

+2

Po zwolnieniu części stosu jest ona całkowicie nieważna (inne wywołania funkcji, przerwania lub cokolwiek innego mogą spowodować nadpisanie pamięci). –

+4

Możesz myśleć o 'longjmp()' jako "rozszerzonym zwrocie". Pomyślne 'longjmp()' działa jak seria kolejnych powrotów, rozwijając stos wywołań aż do osiągnięcia odpowiedniego 'setjmp()'. Po rozwinięciu ramek stosu wywołań nie są już ważne. Jest to w przeciwieństwie do implementacji coroutines (np. Modula-2) lub kontynuacji (np. Scheme), gdzie stos wywołań pozostaje ważny po skoku gdzie indziej.C i C++ obsługują tylko jeden liniowy stos wywołań, * chyba że * używasz wątków, w których tworzysz wiele niezależnych stosów połączeń. –

5

Norma ta mówi o longjmp() (7.13.2.1 Funkcja longjmp):

Funkcja longjmp przywraca środowisko zachowane przez najnowszym wezwaniem makro setjmp w tym samym wywołania programu z odpowiadający mu argument jmp_buf: . Jeżeli nie doszło do takich inwokacja, lub jeśli funkcja zawierająca wywołanie makra setjmp zakończył realizację w śródrocznym

z przypisem, który wyjaśnia ten kawałek:

Na przykład, przez wykonanie instrukcji return lub ponieważ inne wywołanie longjmp spowodowało przeniesienie do wywołania setjmp w funkcji wcześniejszej w zestawie wywołań zagnieżdżonych.

Więc nie można longjmp() z powrotem & powrotem po zagnieżdżonych setjmp/longjmp zestawów.

+0

Jeśli w ramach funkcji znajduje się wiele zagnieżdżonych zakresów, a funkcja setjmp() jest wykonywana w zasięgu zagnieżdżonym, czy można legalnie longjmp() tam wrócić, jeśli opuścił on zasięg za pomocą setjmp(), ale pozostał w obrębie tej samej funkcji? Czy oznaczałoby to, że zmienne zmienne w zakresie zagnieżdżonym są wymagane do utrzymywania ich wartości, jeśli wykonanie opuszcza i ponownie wchodzi w ten zakres (ale pozostaje w funkcji)? – supercat

Powiązane problemy