How do volatile variables work in C, and why are they critical when writing code that interfaces directly with hardware registers?