Оптимизирующие компиляторы могут переопределить порядок следования инструкций в программе и сохранить конкретные переменные в регистрах центрального процессора, а не в оперативной памяти. Обычно такая оптимизация зависит от потока управления ходом выполнения программы, отражающего лексическую структуру программы. Поскольку операции перехода, выполняемые с помощью вызовов функций setjmp() и longjmp(), создаются и происходят в ходе выполнения программы, оптимизатор компилятора не может взять их в расчет в процессе своей работы. Более того, семантика некоторых реализаций двоичных интерфейсов приложений (ABI) требует, чтобы функция longjmp() восстанавливала копии регистров центрального процессора, сохраненные ранее при вызове функции setjmp().
Это говорит о том, что в результате вызова longjmp() в оптимизированных переменных могут оказаться неверные значения. Убедиться в этом можно, изучив поведение программы, представленной в листинге 6.6.
Листинг 6.6. Демонстрация взаимного влияния оптимизации при компиляции и функции longjmp()
proc/setjmp_vars.c
#include
#include
#include
static jmp_buf env;
static void
doJump(int nvar, int rvar, int vvar)
{
printf("Inside doJump(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
longjmp(env, 1);
}
int
main(int argc, char *argv[])
{
int nvar;
register int rvar; /* По возможности выделяется в регистре */
volatile int vvar; /* Смотрите текст */
nvar = 111;
rvar = 222;
vvar = 333;
if (setjmp(env) == 0) { /* Код, выполняемый после setjmp() */
nvar = 777;
rvar = 888;
vvar = 999;
doJump(nvar, rvar, vvar);
} else { /* Код, выполняемый после longjmp() */
printf("After longjmp(): nvar=%d rvar=%d vvar=%d\n", nvar, rvar, vvar);
}
exit(EXIT_SUCCESS);
}
proc/setjmp_vars.c
При компиляции без оптимизации программы, представленной в листинге 6.6, мы увидим на выходе вполне ожидаемую информацию:
$ cc — o setjmp_vars setjmp_vars.c
$ ./setjmp_vars
Inside doJump(): nvar=777 rvar=888 vvar=999
After longjmp(): nvar=777 rvar=888 vvar=999
Но при компиляции с оптимизацией будут получены такие неожиданные результаты:
$ cc — O — o setjmp_vars setjmp_vars.c
$ ./setjmp_vars
Inside doJump(): nvar=777 rvar=888 vvar=999
After longjmp(): nvar=111 rvar=222 vvar=999
Здесь видно, что после вызова longjmp() переменные nvar и rvar были переопределены, получив значения, имевшиеся у них ко времени вызова функции setjmp(). Это произошло потому, что вследствие вызова longjmp() реорганизация оптимизатором кода привела к путанице. Эта проблема может коснуться любых локальных переменных, являющихся кандидатами на оптимизацию. Как правило, она касается переменных-указателей и переменных любого простого типа: char, int, float и long.
Подобной реорганизации кода можно избежать, объявив переменные изменяемыми — volatile, что даст указание оптимизатору не оптимизировать их. В предыдущем выводе информации из программы было показано, что переменная vvar, объявленная volatile, была правильно обработана даже при компиляции с оптимизацией.
Поскольку различные компиляторы используют различные приемы оптимизации, в портируемых программах в тех функциях, которые вызывают setjmp(), ключевое слово volatile должно указываться со всеми локальными переменными вышеупомянутых типов.
Если компилятору GNU C задать ключ — Wextra (extra warnings — «дополнительные предупреждения»), то в отношении программы setjmp_vars.c он выдаст следующие полезные предупреждения:
$ cc — Wall — Wextra — O — o setjmp_vars setjmp_vars.c
setjmp_vars.c: In function 'main':
setjmp_vars.c:17: warning: variable 'nvar' might be clobbered
by 'longjmp' or 'vfork'
setjmp_vars.c:18: warning: variable 'rvar' might be clobbered
by 'longjmp' or 'vfork'
Поучительно будет взглянуть на ассемблерный выход, создаваемый при компиляции программы setjmp_vars.c как с оптимизацией, так и без нее. Команда cc — S создает файл с расширением. s, где содержится сгенерированный для программы ассемблерный код.