Стандарт C ++ говорит об этом в [intro.execution]:
19 Если обработчик сигнала выполняется в результате вызова функции std::raise
, то выполнение обработчика выполняется после вызова функции std::raise
и до ее возврата. [ Примечание: Когда сигнал принимается по другой причине, выполнение обработчика сигнала обычно не выполняется по сравнению с остальной частью программы. - конечная нота ]
Значение слова «непоследовательность» было разъяснено ранее:
15 ... SNIP ... [ Примечание: Выполнение неупорядоченных оценок может перекрываться. - конечная нота ]
Затем в [intro.races]:
20 Два действия потенциально одновременны если
(20.1) - они выполняются разными потоками, или
(20.2) - они не секвенированы, по крайней мере, один выполняется обработчиком сигнала, и они не выполняются одним и тем же вызов обработчика сигнала.
Выполнение программы содержит гонку данных , если она содержит два потенциально одновременных конфликтующих действия, по крайней мере одно из которых не атоми c, и ни одно из них не происходит раньше другой, за исключением специального случая для обработчиков сигналов, описанных ниже. Любая такая гонка данных приводит к неопределенному поведению.
Особый случай, о котором идет речь:
21 Два доступа к одному и тому же объекту типа volatile std::sig_atomic_t
не приводят к гонка данных, если оба происходят в одном и том же потоке, даже если один или несколько из них встречаются в обработчике сигналов.
Подводя итоги: обработчик сигналов (вызываемый для асинхронного сигнала) обращается к объект со стати c длительностью хранения, который не является атомом c, этот доступ не секвенирован, и когда это происходит одновременно с конфликтующим доступом (к тому же объекту, например), тогда возникает гонка данных, что приводит к неопределенному поведению.
Обратите внимание, что это может происходить так же, как в однопоточном приложении, так и в многопоточном приложении. Пример (замените int
любым другим типом, который более явно не атоми c, если необходимо):
#include <csignal>
int global = 0;
void signal_handler(int signal) {
global = 0; // OOPS : this access is (typically) unsequenced
// and might happen concurrently with the access
// in main, when the interrupt happens right in
// the middle of that access
}
int main(void) {
std::signal(SIGINT, signal_handler);
while (true) {
++global; // potentially concurrent access
}
return 0;
}