J
jacob navia
The stack is one of the central structures in C. Continuing this series
of installments, we come to the interactions of longjmp/setjmp (and also
the lowly "goto" statement) with the stack.
As you know, longjmp/setjmp allow a non-local transfer of control from
a point in the execution of the program to another, completely unrelated
point using a previously established context.
One of the problems with the couple of functions in question (setjmp
establishes this context, longjmp uses a context to return to the
point where the context was established) is a situation like this:
int fn(void)
{
char *m = malloc(BUFSIZ);
fn1(m);
free(m);
}
This looks perfectly correct, no leaks.
Well, the problem arises when fn1 makes a longjmp to somewhere else,
completely bypassing the free() instruction and making for a memory
leak.
One of the advantages of alloca() by the way, is that alloca() will NOT
produce a leak in this case. But obviously too, the call to "malloc"
is just an example of resource allocation. fopen() could have been
called instead of malloc, and we would have the same problem.
There are several solutions for this, one of them would be to indicate
a piece of code that should be executed in the case of a non local
jump across this function.
This is the solution proposed by C's cousin, C++. The run time "walks
upwards" the stack, executing all code marked for execution in this
case. This is not possible in standard C (yet) but there are proposals
that try to introduce the famous try/catch couple into C.
In any case there is absolutely NO solution for this problem within
standard C.
But even without longjmp/setjmp, a simple "goto" can make things quite
messy:
goto lab3; // invalid: going INTO scope of VLA.
{
double a[n];
a[j] = 4.4;
lab3:
a[j] = 3.3;
goto lab4; // valid: going WITHIN scope of VLA.
a[j] = 5.5;
lab4:
a[j] = 6.6;
}
goto lab4; // invalid: going INTO scope of VLA
This rules are specified by the standard to preserve the integrity
of the stack. If there is an implementation that increases/decreases
dynamically the stack, jumping into the middle of such a block
bypassing the stack adjustment can only crash the program!
Note that the problem *remains* even if you do NOT use C99 t all:
{
double a[5];
a[j] = 4.4;
lab3:
a[j] = 3.3;
goto lab4; // valid: going WITHIN scope of VLA.
a[j] = 5.5;
lab4:
a[j] = 6.6;
}
In an implementation that increases/decreases the stack pointer when
such a block is found, the goto into the middle of the block would
bypass the stack adjusting anyway, even if it is a fixed amount and
not a variable one as above.
What are the general rules that can be followed to avoid problems?
As I said above, there is NO standard solution for the longjmp/setjmp
problem within the ISO subset of the language
For the goto, the solution is quite obvious:
"Never do a goto into the middle of a block"
of installments, we come to the interactions of longjmp/setjmp (and also
the lowly "goto" statement) with the stack.
As you know, longjmp/setjmp allow a non-local transfer of control from
a point in the execution of the program to another, completely unrelated
point using a previously established context.
One of the problems with the couple of functions in question (setjmp
establishes this context, longjmp uses a context to return to the
point where the context was established) is a situation like this:
int fn(void)
{
char *m = malloc(BUFSIZ);
fn1(m);
free(m);
}
This looks perfectly correct, no leaks.
Well, the problem arises when fn1 makes a longjmp to somewhere else,
completely bypassing the free() instruction and making for a memory
leak.
One of the advantages of alloca() by the way, is that alloca() will NOT
produce a leak in this case. But obviously too, the call to "malloc"
is just an example of resource allocation. fopen() could have been
called instead of malloc, and we would have the same problem.
There are several solutions for this, one of them would be to indicate
a piece of code that should be executed in the case of a non local
jump across this function.
This is the solution proposed by C's cousin, C++. The run time "walks
upwards" the stack, executing all code marked for execution in this
case. This is not possible in standard C (yet) but there are proposals
that try to introduce the famous try/catch couple into C.
In any case there is absolutely NO solution for this problem within
standard C.
But even without longjmp/setjmp, a simple "goto" can make things quite
messy:
goto lab3; // invalid: going INTO scope of VLA.
{
double a[n];
a[j] = 4.4;
lab3:
a[j] = 3.3;
goto lab4; // valid: going WITHIN scope of VLA.
a[j] = 5.5;
lab4:
a[j] = 6.6;
}
goto lab4; // invalid: going INTO scope of VLA
This rules are specified by the standard to preserve the integrity
of the stack. If there is an implementation that increases/decreases
dynamically the stack, jumping into the middle of such a block
bypassing the stack adjustment can only crash the program!
Note that the problem *remains* even if you do NOT use C99 t all:
{
double a[5];
a[j] = 4.4;
lab3:
a[j] = 3.3;
goto lab4; // valid: going WITHIN scope of VLA.
a[j] = 5.5;
lab4:
a[j] = 6.6;
}
In an implementation that increases/decreases the stack pointer when
such a block is found, the goto into the middle of the block would
bypass the stack adjusting anyway, even if it is a fixed amount and
not a variable one as above.
What are the general rules that can be followed to avoid problems?
As I said above, there is NO standard solution for the longjmp/setjmp
problem within the ISO subset of the language
For the goto, the solution is quite obvious:
"Never do a goto into the middle of a block"