Ответ 1
В общем случае сложно компилятору точно знать, к каким объектам может иметь доступ функция, и, следовательно, потенциально может измениться. В точке, где вызывается putchar()
, GCC не знает, может ли быть реализация putchar()
, которая может изменить running
, поэтому она должна быть несколько пессимистичной и предположить, что running
может фактически иметь был изменен.
Например, может быть реализация putchar()
позже в блоке трансляции:
int putchar( int c)
{
running = c;
return c;
}
Даже если в блоке перевода не реализована реализация putchar()
, может быть что-то, что может, например, передать адрес объекта running
таким образом, чтобы putchar
мог его изменить:
void foo(void)
{
set_putchar_status_location( &running);
}
Обратите внимание, что ваша функция handler()
доступна на глобальном уровне, поэтому putchar()
может вызывать handler()
сам (прямо или иначе), что является примером вышеуказанной ситуации.
С другой стороны, поскольку running
видна только для трансляционной единицы (будучи static
), к тому времени, когда компилятор дойдет до конца файла, он сможет определить, что нет возможности для putchar()
для доступа к нему (при условии, что случай), и компилятор может вернуться и "исправить" пессимизацию в цикле while.
Забастовкa >
Так как running
является статическим, компилятор может определить, что он недоступен из-за пределов единицы перевода и делает оптимизацию, о которой вы говорите. Однако, поскольку доступ к нему через handler()
и handler()
доступен извне, компилятор не может оптимизировать доступ. Даже если вы ставите handler()
static, он доступен извне, поскольку вы передаете его адрес другой функции.
Обратите внимание, что в первом примере, хотя все же верно то, что я упомянул в предыдущем параграфе, компилятор может оптимизировать доступ к running
, потому что "модель абстрактной машины" на языке C основана не на с учетом асинхронной активности, за исключением очень ограниченных условий (одним из которых является ключевое слово volatile
, а другое - обработка сигналов, хотя требования обработки сигналов недостаточно сильны, чтобы компилятор не смог оптимизировать доступ к running
в вашем первом примере).
Фактически, здесь что-то C99 говорит об абстрактном механизме поведения в почти этих точках:
5.1.2.3/8 "Выполнение программы"
Пример 1:Реализация может определять взаимно однозначное соответствие между абстрактной и фактической семантикой: в каждой точке последовательности значения фактических объектов согласуются с значениями, указанными абстрактной семантикой. Тогда ключевое слово
volatile
будет избыточным.В качестве альтернативы реализация может выполнять различные оптимизации внутри каждой единицы перевода, так что фактическая семантика согласуется с абстрактной семантикой только при выполнении вызовов функций через границы единицы перевода. В такой реализации во время каждой записи функции и функции возвращаются туда, где вызывающая функция и вызываемая функция находятся в разных единицах перевода, значения всех внешних объектов и всех объектов, доступных через указатели, согласуются с абстрактной семантикой, Кроме того, во время каждой такой записи функции значения параметров вызываемой функции и всех объектов, доступных через указатели в ней, согласуются с абстрактной семантикой. В этом типе реализации объекты, на которые ссылаются подпрограммы обработки прерываний, активируемые сигнальной функцией, требуют явной спецификации энергозависимого хранилища, а также других ограничений, определенных для реализации.
Наконец, вы должны отметить, что в стандарте C99 также говорится:
7.14.1.1/5 "Функция
signal
Если сигнал возникает иначе, чем в результате вызова функции
abort
илиraise
, поведение undefined, если обработчик сигнала ссылается на любой объект со статической продолжительностью хранения, отличной от назначения значения для объект, объявленный какvolatile sig_atomic_t
...
Так что, строго говоря, переменную running
может потребоваться объявить как:
volatile sig_atomic_t running = 1;