В общем случае компилятору сложно точно знать, к каким объектам функция может иметь доступ и, следовательно, потенциально может измениться. В тот момент, когда вызывается 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.
Поскольку running
является статическим, компилятор может определить, что он недоступен извне модуля перевода, и выполнить оптимизацию, о которой вы говорите. Однако, поскольку он доступен через handler()
, а handler()
доступен извне, компилятор не может оптимизировать доступ. Даже если вы сделаете handler()
статическим, он будет доступен извне, так как вы передадите его адрес другой функции.
Обратите внимание, что в вашем первом примере, хотя то, что я упомянул в предыдущем абзаце, все еще верно, компилятор может оптимизировать доступ к running
, потому что «модель абстрактной машины», на которой основан язык C, не требует учитывать асинхронную активность, за исключением очень ограниченных обстоятельств (одним из которых является ключевое слово volatile
, а другим - обработка сигналов, хотя требования к обработке сигналов недостаточно строгие, чтобы компилятор не мог оптимизировать доступ к running
в вашем первом примере).
На самом деле, вот что C99 говорит о поведении абстрактной машины в этих точных условиях:
5.1.2.3 / 8 "Выполнение программы"
ПРИМЕР 1:
Реализация может определять взаимно-однозначное соответствие между абстрактной и фактической семантикой: в каждой точке последовательности значения фактических объектов будут соответствовать значениям, указанным в абстрактной семантике. Ключевое слово volatile
будет тогда излишним.
В качестве альтернативы реализация может выполнять различные оптимизации внутри каждой единицы перевода, так что фактическая семантика согласуется с абстрактной семантикой только при выполнении вызовов функций через границы единиц перевода. В такой реализации во время каждой записи функции и возврата функции, когда вызывающая функция и вызываемая функция находятся в разных единицах перевода, значения всех внешних связанных объектов и всех объектов, доступных через указатели в них, будут согласовываться с абстрактной семантикой , Кроме того, во время каждой такой записи функции значения параметров вызываемой функции и всех объектов, доступных через указатели в ней, будут согласовываться с абстрактной семантикой. В этом типе реализации объекты, на которые ссылаются подпрограммы обслуживания прерываний, активированные функцией сигнала, потребуют явной спецификации энергозависимой памяти, а также других ограничений, определенных реализацией.
Наконец, вы должны отметить, что стандарт C99 также гласит:
7.14.1.1 / 5 "Функция signal
`
Если сигнал возникает не в результате вызова функции abort
или raise
, поведение не определено, если обработчик сигнала ссылается на любой объект со статической продолжительностью хранения, кроме как путем присвоения значения объявленному объекту как volatile sig_atomic_t
...
Строго говоря, переменная running
может потребоваться объявить как:
volatile sig_atomic_t running = 1;