C/setjmp.h — различия между версиями

Материал из C\C++ эксперт
Перейти к: навигация, поиск
 
Строка 1: Строка 1:
* [[C/setjmp.h/longjmp | longjmp]]
+
{{C Standard library}}
* [[C/setjmp.h/setjmp | setjmp]]
+
 
 +
'''setjmp.h''' is a header defined in the C standard library to provide "non-local jumps," or control flow besides the usual subroutine call and return sequence. The paired functions '''<code>setjmp</code>''' and '''<code>longjmp</code>''' provide this functionality. First <code>setjmp</code> saves the environment of the calling function into a data structure, and then <code>longjmp</code> can use this structure to "jump" back to the point it was created, at the <code>setjmp</code> call.
 +
 
 +
The typical use for <code>setjmp</code>/<code>longjmp</code> is for exception handling &mdash; by calling <code>longjmp</code>, the program can jump out of many levels of nested function calls without having to go to the trouble of setting flag variables which need to be checked in each function. A problem with the use of <code>setjmp</code>/<code>longjmp</code> is that cleanup (such as closing file descriptors, flushing buffers, freeing heap-allocated memory, etc.) is not conducted automatically.
 +
 
 +
Another not-so-common use of <code>setjmp</code> is to create syntax similar to coroutines.
 +
 
 +
On systems that support signal handling, POSIX.1 does not specify whether <code>setjmp</code> and <code>longjmp</code> save or restore the current set of blocked signals &mdash; if a program employs signal handling it should use POSIX's <code>sigsetjmp</code>/<code>siglongjmp</code>.
 +
 
 +
Compared to mechanisms in higher-level programming languages such as Python, Java, C++, C#, and particularly in older high-level languages such as Algol 60 and Common Lisp, the <code>setjmp</code>/<code>longjmp</code> technique is archaic. These languages provide more powerful exception handling techniques, while languages such as Scheme, Smalltalk, and Haskell provide even more general continuation-handling constructs.
 +
 
 +
 
 +
==Member functions==
 +
 
 +
{| class = wikitable
 +
|-
 +
|<code>int setjmp(jmp_buf&nbsp;env)</code> || Sets up the local <code>jmp_buf</code> buffer and initializes it for the jump. This routine<ref name="macro">ISO C states that <code>setjmp</code> must be implemented as a macro, but POSIX explicitly states that it is undefined whether <code>setjmp</code> is a macro or a function.</ref> saves the program's calling environment in the environment buffer specified by the <code>env</code> argument for later use by <code>longjmp</code>. If the return is from a direct invocation, <code>setjmp</code> returns 0. If the return is from a call to <code>longjmp</code>, <code>setjmp</code> returns a nonzero value.
 +
|-
 +
|<code>void longjmp(jmp_buf&nbsp;env, int&nbsp;value)</code> || Restores the context of the environment buffer <code>env</code> that was saved by invocation of the <code>setjmp</code> routine<ref name="macro"/> in the same invocation of the program. Invoking longjmp from a nested signal handler is undefined. The value specified by <code>value</code> is passed from <code>longjmp</code> to <code>setjmp</code>. After <code>longjmp</code> is completed, program execution continues as if the corresponding invocation of <code>setjmp</code> had just returned. If the <code>value</code> passed to <code>longjmp</code> is 0, <code>setjmp</code> will behave as if it had returned 1; otherwise, it will behave as if it had returned <code>value</code>.
 +
|-
 +
|}
 +
 
 +
<code>setjmp</code> saves the current execution state into a structure of type <code>jmp_buf</code>; later, a <code>longjmp</code> call can transfer control to the point immediately after the call to <code>setjmp</code>. The (apparent) return value from <code>setjmp</code> indicates whether control reached that point normally or from a call to <code>longjmp</code>. This leads to a common idiom: <code>if( setjmp(x) ){/* handle longjmp(x) */}</code>.
 +
 
 +
==Member types==
 +
 
 +
{| class = wikitable
 +
|-
 +
|<code>jmp_buf</code> || An array type, such as <code>struct __jmp_buf_tag</code><ref>This is the type used by the GNU C Library, version 2.7</ref>, suitable for holding the information needed to restore a calling environment.
 +
|}
 +
 
 +
The C99 Rationale describes <code>jmp_buf</code> as being an array type for backwards compatibility; existing code refers to <code>jmp_buf</code> storage locations by name (without the <code>&</code> address-of operator), which is only possible for array types.
 +
 
 +
==Caveats==
 +
If the function in which setjmp/sigsetjmp was called returns, it is no longer possible to safely use longjmp/siglongjmp with the corresponding jmp_buf/sigjmp_buf object. This is because the stack frame is invalidated when the function returns. Calling longjmp/siglongjmp restores the stack pointer, which—because the function returned—would point to a non-existent and potentially overwritten/corrupted stack frame.
 +
 
 +
Similarly, C99 does not require that longjmp/siglongjmp preserve the current stack frame. This means that jumping into a function which was exited via a call to longjmp/siglongjmp is undefined. However, most implementations of longjmp/siglongjmp leave the stack frame intact, allowing setjmp/sigsetjmp and longjmp/siglongjmp to be used to jump back-and-forth between two or more functions—a feature exploited for multitasking.
 +
 
 +
== Simple example ==
 +
This example shows the basic idea of setjmp. Main calls first, which in turn calls second. The "second" function jumps back into main, skipping "first"'s print statement.
 +
 
 +
<source lang="c">
 +
#include <stdio.h>
 +
#include <setjmp.h>
 +
 
 +
static jmp_buf buf;
 +
 
 +
void second(void) {
 +
    printf("second\n");        // prints
 +
    longjmp(buf,1);            // jumps back to where setjmp was called - making setjmp now return 1
 +
}
 +
 
 +
void first(void) {
 +
    second();
 +
    printf("first\n");          // does not print
 +
}
 +
 
 +
int main() { 
 +
    if ( ! setjmp(buf) ) {
 +
        first();                // when executed, setjmp returns 0
 +
    } else {                    // when longjmp jumps back, setjmp returns 1
 +
        printf("main");        // prints
 +
    }
 +
 
 +
    return 0;
 +
}
 +
</source>
 +
When executed, the above program will output:
 +
second
 +
main
 +
Notice that although the <code>first()</code> subroutine gets called, "<code>first</code>" never is printed. "<code>main</code>" gets printed as the conditional statement <code>if ( ! setjmp(buf) )</code> is executed a second time.
 +
 
 +
== Exception handling ==
 +
In this example, <code>setjmp</code> is used to bracket exception handling, like <code>try</code> in some other languages. The call to <code>longjmp</code> is analogous to a <code>throw</code> statement, allowing an exception to return an error status directly to the <code>setjmp</code>. The following code adheres to the 1999 ISO C standard and SUS by invoking <code>setjmp</code> in a limited range of contexts:<ref name="SUS">{{man|sh|setjmp|SUS|set jump point for a non-local goto}}</ref>
 +
*As the condition to an <code>if</code>, <code>switch</code> or iteration statement
 +
*As above in conjunction with a single <code>!</code> or comparison with an integer constant
 +
*As a statement (with the return value unused)
 +
Following these rules can make it easier for the implementation to create the environment buffer, which can be a sensitive operation.<ref>[http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf C99 Rationale, version 5.10, April 2003], section 7.13.1</ref> More general use of <code>setjmp</code> can cause undefined behaviour, such as corruption of local variables; conforming compilers and environments are not required to protect or even warn against such usage. However, slightly more sophisticated idioms such as <code>switch ((exception_type = setjmp(env))) { }</code> are common in literature and practice, and remain relatively portable. A simple conforming methodology is presented below, where an additional variable is maintained along with the state buffer. This variable could be elaborated into a structure incorporating the buffer itself.
 +
 
 +
<source lang="c">
 +
#include <stdio.h>
 +
#include <stdlib.h>
 +
#include <string.h>
 +
#include <setjmp.h>
 +
 +
void first(void);
 +
void second(void);
 +
 +
/* This program's output is:
 +
 +
calling first
 +
calling second
 +
entering second
 +
second failed with type 3 exception; remapping to type 1.
 +
first failed, exception type 1
 +
 +
*/
 +
 +
/* Use a file scoped static variable for the exception stack so we can access
 +
* it anywhere within this translation unit. */
 +
static jmp_buf exception_env;
 +
static int exception_type;
 +
 +
int main(int argc, char **argv) {
 +
    volatile void *mem_buffer;
 +
 +
    mem_buffer = NULL;
 +
    if (setjmp(exception_env)) {
 +
        /* if we get here there was an exception */
 +
        printf("first failed, exception type %d\n", exception_type);
 +
    } else {
 +
        /* Run code that may signal failure via longjmp. */
 +
        printf("calling first\n");
 +
        first();
 +
        mem_buffer = malloc(300); /* allocate a resource */
 +
        printf(strcpy((char*) mem_buffer, "first succeeded!")); /* ... this will not happen */
 +
    }
 +
    if (mem_buffer)
 +
        free((void*) mem_buffer); /* carefully deallocate resource */
 +
    return 0;
 +
}
 +
 +
void first(void) {
 +
    jmp_buf my_env;
 +
 +
    printf("calling second\n");
 +
    memcpy(my_env, exception_env, sizeof(jmp_buf));
 +
    switch (setjmp(exception_env)) {
 +
        case 3:
 +
            /* if we get here there was an exception. */
 +
            printf("second failed with type 3 exception; remapping to type 1.\n");
 +
            exception_type = 1;
 +
 
 +
        default: /* fall through */
 +
            memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
 +
            longjmp(exception_env, exception_type); /* continue handling the exception */
 +
 
 +
        case 0:
 +
            /* normal, desired operation */
 +
            second();
 +
            printf("second succeeded\n");  /* not reached */
 +
    }
 +
    memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
 +
}
 +
 +
void second(void) {
 +
    printf("entering second\n" ); /* reached */
 +
    exception_type = 3;
 +
    longjmp(exception_env, exception_type); /* declare that the program has failed */
 +
    printf("leaving second\n"); /* not reached */
 +
}
 +
</source>
 +
 
 +
==Cooperative multitasking==
 +
[[C99]] provides that <code>longjmp</code> is guaranteed to work only when the destination is a calling function, i.e., that the destination scope is guaranteed to be intact. Jumping to a function that has already terminated by <code>return</code> or <code>longjmp</code> is undefined.<ref name="ISO/IEC 9899:1999"/> However, most implementations of <code>longjmp</code> do not specifically destroy local variables when performing the jump. Since the context survives until its local variables are erased, it could actually be restored by <code>setjmp</code>. In many environments (such as Really Simple Threads and TinyTimbers), idioms such as <code>if(!setjmp(child_env)) longjmp(caller_env);</code> can allow a called function to effectively pause-and-resume at a <code>setjmp</code>.
 +
 
 +
This is exploited by thread libraries to provide cooperative multitasking facilities without using <code>setcontext</code> or other fiber facilities.  Whereas <code>setcontext</code> is a library service which can create an execution context in heap-allocated memory and can support other services such as buffer overflow protection, abuse of <code>setjmp</code> is implemented by the programmer, who may reserve memory on the stack and fail to notify the library or operating system of the new operating context. On the other hand, a library's implementation of <code>setcontext</code> may internally use <code>setjmp</code> in a fashion similar to this example to save and restore a context, after it has been initialised somehow.
 +
 
 +
Considering that <code>setjmp</code> to a child function will generally work unless sabotaged, and <code>setcontext</code>, as part of POSIX, is not required to be provided by C implementations, this mechanism may be portable where the <code>setcontext</code> alternative fails.
 +
 
 +
Since no exception will be generated upon overflow of one of the multiple stacks in such a mechanism, it is essential to overestimate the space required for each context, including the one containing <code>main()</code> and including space for any signal handlers that might interrupt regular execution. Exceeding the allocated space will corrupt the other contexts, usually with the outermost functions first. Unfortunately, systems requiring this kind of programming strategy are often also small ones with limited resources.
 +
 
 +
<source lang="c">
 +
#include <setjmp.h>
 +
#include <stdio.h>
 +
 
 +
jmp_buf mainTask, childTask;
 +
 
 +
void call_with_cushion(void);
 +
void child(void);
 +
 
 +
int main(void) {
 +
    if (!setjmp(mainTask)) {
 +
        call_with_cushion(); /* child never returns */ /* yield */
 +
    } /* execution resumes after this "}" after first time that child yields */
 +
    for (;;) {
 +
        printf("Parent\n");
 +
        if (!setjmp(mainTask)) {
 +
            longjmp(childTask, 1); /* yield - note that this is undefined under C99 */
 +
        }
 +
    }
 +
}
 +
 
 +
 
 +
void call_with_cushion (void) {
 +
    char space[1000]; /* Reserve enough space for main to run */
 +
    space[999] = 1; /* Do not optimize array out of existence */
 +
    child();
 +
}
 +
 
 +
void child (void) {
 +
    for (;;) {
 +
        printf("Child loop begin\n");
 +
        if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
 +
 
 +
        printf("Child loop end\n");
 +
        if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
 +
    }
 +
    /* Don't return. Instead we should set a flag to indicate that main()
 +
      should stop yielding to us and then longjmp(mainTask, 1) */
 +
}
 +
</source>

Текущая версия на 15:36, 22 июня 2010

setjmp.h is a header defined in the C standard library to provide "non-local jumps," or control flow besides the usual subroutine call and return sequence. The paired functions setjmp and longjmp provide this functionality. First setjmp saves the environment of the calling function into a data structure, and then longjmp can use this structure to "jump" back to the point it was created, at the setjmp call.

The typical use for setjmp/longjmp is for exception handling — by calling longjmp, the program can jump out of many levels of nested function calls without having to go to the trouble of setting flag variables which need to be checked in each function. A problem with the use of setjmp/longjmp is that cleanup (such as closing file descriptors, flushing buffers, freeing heap-allocated memory, etc.) is not conducted automatically.

Another not-so-common use of setjmp is to create syntax similar to coroutines.

On systems that support signal handling, POSIX.1 does not specify whether setjmp and longjmp save or restore the current set of blocked signals — if a program employs signal handling it should use POSIX's sigsetjmp/siglongjmp.

Compared to mechanisms in higher-level programming languages such as Python, Java, C++, C#, and particularly in older high-level languages such as Algol 60 and Common Lisp, the setjmp/longjmp technique is archaic. These languages provide more powerful exception handling techniques, while languages such as Scheme, Smalltalk, and Haskell provide even more general continuation-handling constructs.


Member functions

int setjmp(jmp_buf env) Sets up the local jmp_buf buffer and initializes it for the jump. This routine<ref name="macro">ISO C states that setjmp must be implemented as a macro, but POSIX explicitly states that it is undefined whether setjmp is a macro or a function.</ref> saves the program's calling environment in the environment buffer specified by the env argument for later use by longjmp. If the return is from a direct invocation, setjmp returns 0. If the return is from a call to longjmp, setjmp returns a nonzero value.
void longjmp(jmp_buf env, int value) Restores the context of the environment buffer env that was saved by invocation of the setjmp routine<ref name="macro"/> in the same invocation of the program. Invoking longjmp from a nested signal handler is undefined. The value specified by value is passed from longjmp to setjmp. After longjmp is completed, program execution continues as if the corresponding invocation of setjmp had just returned. If the value passed to longjmp is 0, setjmp will behave as if it had returned 1; otherwise, it will behave as if it had returned value.

setjmp saves the current execution state into a structure of type jmp_buf; later, a longjmp call can transfer control to the point immediately after the call to setjmp. The (apparent) return value from setjmp indicates whether control reached that point normally or from a call to longjmp. This leads to a common idiom: if( setjmp(x) ){/* handle longjmp(x) */}.

Member types

jmp_buf An array type, such as struct __jmp_buf_tag<ref>This is the type used by the GNU C Library, version 2.7</ref>, suitable for holding the information needed to restore a calling environment.

The C99 Rationale describes jmp_buf as being an array type for backwards compatibility; existing code refers to jmp_buf storage locations by name (without the & address-of operator), which is only possible for array types.

Caveats

If the function in which setjmp/sigsetjmp was called returns, it is no longer possible to safely use longjmp/siglongjmp with the corresponding jmp_buf/sigjmp_buf object. This is because the stack frame is invalidated when the function returns. Calling longjmp/siglongjmp restores the stack pointer, which—because the function returned—would point to a non-existent and potentially overwritten/corrupted stack frame.

Similarly, C99 does not require that longjmp/siglongjmp preserve the current stack frame. This means that jumping into a function which was exited via a call to longjmp/siglongjmp is undefined. However, most implementations of longjmp/siglongjmp leave the stack frame intact, allowing setjmp/sigsetjmp and longjmp/siglongjmp to be used to jump back-and-forth between two or more functions—a feature exploited for multitasking.

Simple example

This example shows the basic idea of setjmp. Main calls first, which in turn calls second. The "second" function jumps back into main, skipping "first"'s print statement.

<source lang="c">

  1. include <stdio.h>
  2. include <setjmp.h>

static jmp_buf buf;

void second(void) {

   printf("second\n");         // prints
   longjmp(buf,1);             // jumps back to where setjmp was called - making setjmp now return 1

}

void first(void) {

   second();
   printf("first\n");          // does not print

}

int main() {

   if ( ! setjmp(buf) ) {
       first();                // when executed, setjmp returns 0
   } else {                    // when longjmp jumps back, setjmp returns 1
       printf("main");         // prints
   }
   return 0;

} </source> When executed, the above program will output:

second
main

Notice that although the first() subroutine gets called, "first" never is printed. "main" gets printed as the conditional statement if ( ! setjmp(buf) ) is executed a second time.

Exception handling

In this example, setjmp is used to bracket exception handling, like try in some other languages. The call to longjmp is analogous to a throw statement, allowing an exception to return an error status directly to the setjmp. The following code adheres to the 1999 ISO C standard and SUS by invoking setjmp in a limited range of contexts:<ref name="SUS">Шаблон:Man</ref>

  • As the condition to an if, switch or iteration statement
  • As above in conjunction with a single ! or comparison with an integer constant
  • As a statement (with the return value unused)

Following these rules can make it easier for the implementation to create the environment buffer, which can be a sensitive operation.<ref>C99 Rationale, version 5.10, April 2003, section 7.13.1</ref> More general use of setjmp can cause undefined behaviour, such as corruption of local variables; conforming compilers and environments are not required to protect or even warn against such usage. However, slightly more sophisticated idioms such as switch ((exception_type = setjmp(env))) { } are common in literature and practice, and remain relatively portable. A simple conforming methodology is presented below, where an additional variable is maintained along with the state buffer. This variable could be elaborated into a structure incorporating the buffer itself.

<source lang="c">

  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include <setjmp.h>

void first(void); void second(void);

/* This program's output is:

calling first calling second entering second second failed with type 3 exception; remapping to type 1. first failed, exception type 1

  • /

/* Use a file scoped static variable for the exception stack so we can access

* it anywhere within this translation unit. */

static jmp_buf exception_env; static int exception_type;

int main(int argc, char **argv) {

   volatile void *mem_buffer;

   mem_buffer = NULL;
   if (setjmp(exception_env)) {
       /* if we get here there was an exception */
       printf("first failed, exception type %d\n", exception_type);
   } else {
       /* Run code that may signal failure via longjmp. */
       printf("calling first\n");
       first();
       mem_buffer = malloc(300); /* allocate a resource */
       printf(strcpy((char*) mem_buffer, "first succeeded!")); /* ... this will not happen */
   }
   if (mem_buffer)
       free((void*) mem_buffer); /* carefully deallocate resource */
   return 0;

}

void first(void) {

   jmp_buf my_env;

   printf("calling second\n");
   memcpy(my_env, exception_env, sizeof(jmp_buf));
   switch (setjmp(exception_env)) {
       case 3:
           /* if we get here there was an exception. */
           printf("second failed with type 3 exception; remapping to type 1.\n");
           exception_type = 1;
       default: /* fall through */
           memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
           longjmp(exception_env, exception_type); /* continue handling the exception */
       case 0:
           /* normal, desired operation */
           second();
           printf("second succeeded\n");  /* not reached */
   }
   memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */

}

void second(void) {

   printf("entering second\n" ); /* reached */
   exception_type = 3;
   longjmp(exception_env, exception_type); /* declare that the program has failed */
   printf("leaving second\n"); /* not reached */

} </source>

Cooperative multitasking

C99 provides that longjmp is guaranteed to work only when the destination is a calling function, i.e., that the destination scope is guaranteed to be intact. Jumping to a function that has already terminated by return or longjmp is undefined.<ref name="ISO/IEC 9899:1999"/> However, most implementations of longjmp do not specifically destroy local variables when performing the jump. Since the context survives until its local variables are erased, it could actually be restored by setjmp. In many environments (such as Really Simple Threads and TinyTimbers), idioms such as if(!setjmp(child_env)) longjmp(caller_env); can allow a called function to effectively pause-and-resume at a setjmp.

This is exploited by thread libraries to provide cooperative multitasking facilities without using setcontext or other fiber facilities. Whereas setcontext is a library service which can create an execution context in heap-allocated memory and can support other services such as buffer overflow protection, abuse of setjmp is implemented by the programmer, who may reserve memory on the stack and fail to notify the library or operating system of the new operating context. On the other hand, a library's implementation of setcontext may internally use setjmp in a fashion similar to this example to save and restore a context, after it has been initialised somehow.

Considering that setjmp to a child function will generally work unless sabotaged, and setcontext, as part of POSIX, is not required to be provided by C implementations, this mechanism may be portable where the setcontext alternative fails.

Since no exception will be generated upon overflow of one of the multiple stacks in such a mechanism, it is essential to overestimate the space required for each context, including the one containing main() and including space for any signal handlers that might interrupt regular execution. Exceeding the allocated space will corrupt the other contexts, usually with the outermost functions first. Unfortunately, systems requiring this kind of programming strategy are often also small ones with limited resources.

<source lang="c">

  1. include <setjmp.h>
  2. include <stdio.h>

jmp_buf mainTask, childTask;

void call_with_cushion(void); void child(void);

int main(void) {

   if (!setjmp(mainTask)) {
       call_with_cushion(); /* child never returns */ /* yield */
   } /* execution resumes after this "}" after first time that child yields */
   for (;;) {
       printf("Parent\n");
       if (!setjmp(mainTask)) {
           longjmp(childTask, 1); /* yield - note that this is undefined under C99 */
       }
   }

}


void call_with_cushion (void) {

   char space[1000]; /* Reserve enough space for main to run */
   space[999] = 1; /* Do not optimize array out of existence */
   child();

}

void child (void) {

   for (;;) {
       printf("Child loop begin\n");
       if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
       printf("Child loop end\n");
       if (!setjmp(childTask)) longjmp(mainTask, 1); /* yield - invalidates childTask in C99 */
   }
   /* Don't return. Instead we should set a flag to indicate that main()
      should stop yielding to us and then longjmp(mainTask, 1) */

}

</source>