The risk you run in the scenario you describe (read from memory to register, update register, write to memory and a context switch happens between any of those operations) is that you could lose an update made in the other context.
For example:
main context:
read i (=10) from memory to register R1
add 5 to R1
<interrupt. Switch to interrupt context>
read i (=10) from memory to register R1
add 10 to R1
write R1 to i in memory (i = 20)
<end of interrupt. Back to main context>
write R1 to i in memory (i = 15)
As you can see, the update from the interrupt has been lost.
An even bigger problem would occur if your type requires multiple operations to write it to memory and the interrupt occurs in the middle of a write operation.
For example:
main context:
read first half of i (=10) from memory to register R1
read second half of i (=10) from memory to register R2
add 5 to R1/R2 pair
write R1 to first half of i in memory
<interrupt. Switch to interrupt context>
read first half of i (= ??) from memory to register R1
read second half of i (= ??) from memory to register R2
add 10 to R1/R2 pair
write R1 to first half of i in memory
write R2 to second half of i in memory
<end of interrupt. Back to main context>
write R2 to second half of i in memory
Here, there is no telling what value i will end up with.
With sig_atomic_t
, this second problem can't occur, because the type is guaranteed to use atomic read/write operations.