Почему этот вывод компилятора неверен, когда используется setjmp? - PullRequest
0 голосов
/ 18 июня 2019

Учитывая этот код C

#include <stdio.h>
#include <setjmp.h>
void foo(int x) {
  jmp_buf env;
  if (setjmp(env) == 0) {
    printf("%d\n", 23);
    longjmp(env, 1);
  } else {
    printf("%d\n", x);
  }
}

В результате должно быть напечатано 23, а затем x, и все должно быть четко определено.

Но допустим, что компилятор не знает, чтоsetjmp / longjmp являются специальными функциями, и он генерирует следующий код:

;function foo
;r0 : int x

foo:
    sub sp, sp, #sizeof(jmp_buf) ; reserve space for env
    push r0         ; save x for later
    add r0, sp, #4  ; load address of env
    call setjmp
    pop r1          ; restore SP, move x to r1 <<== corrupt after jongjmp
    cmp r0, #0      ; if (setjmp(env) == 0)
    bne 1f
    lea r0, "%d\n"  ; printf("%d\n", 23)
    mov r1, #23
    call printf
    mov r0, sp  ; load address of env
    mov r1, #1
    call longjmp
    b 2f
1:
    lea r0, "%d\n"      ; printf("%\dn", x), x already in r1
    call printf
2:
    add sp, sizeof(jmp_buf)
    ret

Это выдает 23, как и ожидалось, но затем печатает адрес повторного запуска вызова longjmp, то есть адрес метки 1.

Переменная x временно сохраняется в стеке, чтобы сохранить ее при вызове функции setjmp (r0, будучи регистром аргументов, сохраняется вызывающей стороной).Я думаю, что это вполне допустимая вещь для компилятора.Но так как setjmp возвращает дважды, это повреждает переменную, в то время как стандарт C говорит, что не должен.

Ответы [ 2 ]

2 голосов
/ 19 июня 2019

setjmp - это макрос, а не функция, которая признается стандартом, что в некоторых реализациях могут потребоваться функции, недоступные для обычных функций.

Стандарт явно разрешает макросу просто расширяться до функции с тем же именем, для случая реализаций, в которых он может быть реализован с помощью функции, использующей стандартную семантику вызова.Однако если прикладная программа пытается обойти макрос, либо с помощью #undef, либо с помощью (setjmp)(jmpbuf), это приводит к неопределенному поведению.Это противоположно обычным стандартным библиотечным функциям, которые также могут быть реализованы как макросы, так и функции, но к которым можно получить доступ, используя вышеуказанные методы, чтобы избежать расширения макроса.

Кроме того, факт, что setjmp являетсяуказанное в качестве макроса означает, что &setbuf также является неопределенным поведением.Фактически, стандарт допускает вызов setbuf только в двух контекстах:

  1. Как оператор полного выражения, возможно с явным приведением к void

  2. В условии оператора if или цикла и только при условии

    • setjmp вызывает себя

    • Оператор ! с аргументом setjmp в качестве аргумента

    • Сравнение между вызовом setjmp и целочисленной константой.

Другими словами, значение вызова setjmp не может быть сохранено или участвовать в арифметике, и никакие другие вычисления не могут быть выполнены внутри точек последовательности, которые окружают контекст вызова.

Таким образом, стандарты дают реализации, имеет много возможностей для реализации setjmp.

0 голосов
/ 19 июня 2019

Почему этот вывод компилятора неверен, когда используется setjmp?

Поскольку компилятор не соответствует стандарту.Поскольку побочные эффекты, возникающие при выполнении сгенерированного машинного кода, не соответствуют спецификации абстрактной машины, изложенной в стандарте C.Ваш код является простым примером, так как printf производит побочный эффект.Работа компиляторов (только), как правило, заключается в переводе предложений с одного языка на другой, чтобы побочные эффекты оставались прежними.Если это не удается, ну, это сломано.

C11 7.13.2.1p3 говорит:

Все доступные объекты имеют значения, а все остальныеКомпоненты абстрактной машины имеют состояние на момент вызова функции longjmp, за исключением того, что значения объектов продолжительности автоматического хранения, локальные для функции, содержащей вызов соответствующего макроса setjmp, которые не имеют тип volatile-qualifiedи были изменены между вызовом setjmp и вызовом longjmp, являются неопределенными.

x имеет автоматическую продолжительность хранения и является локальной переменной в функции и не является изменчивой, но она не изменялась междузвонки setjmp и longjmp.Таким образом, если x имел какое-то значение до вызова longjmp, он должен иметь то же значение после вызова longjmp.

Компилятор, который генерирует код сборки, который не соответствует этой спецификации, не являетсякомпилятор C, или, если говорить более жестко, это вообще не компилятор C.

Есть компиляторы и архитектуры, которые просто не поддерживают вызовы setjmp / longjmp, и, ну, этохорошо.Такие компиляторы прямо заявляют, что они не поддерживают setjmp и, например, у них может отсутствовать заголовок setjmp.h, поэтому его использование невозможно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...