The usual four examples of volatile objects are:
An object that is a memory-mapped I/O port
An object that is shared between multiple concurrent processes
An object that is modified by an asynchronous signal handler
An automatic storage duration object declared in a function that calls setjmp, and whose value is changed between the call to setjmp and a corresponding call to longjmp
The first three examples are all instances of an object with a particular behavior: its value can be modified at any point during the execution of the program. Thus, the seemingly infinite loop:
flag = 1; while (flag);
is valid as long as flag has a volatile qualified type. Presumably, some asynchronous event sets flag to zero in the future. Otherwise, because the value of flag is unchanged within the body of the loop, the compilation system is free to change the above loop into a truly infinite loop that completely ignores the value of flag.
The fourth example, involving variables local to functions that call setjmp, is more involved. The fine print about the behavior of setjmp and longjmp notes that there are no guarantees about the values for objects matching the fourth case. For the most desirable behavior, it is necessary for longjmp to examine every stack frame between the function calling setjmp and the function calling longjmp for saved register values. The possibility of asynchronously created stack frames makes this job even harder.
When an automatic object is declared with a volatile qualified type, the compilation system knows that it has to produce code that exactly matches what the programmer wrote. Therefore, the most recent value for such an automatic object is always in memory and not just in a register, and is guaranteed to be up-to-date when longjmp is called.