Why the need to use the volatile keyword on global variables when handling interrupts in Arduino?
First, it's volatile not Volatile. I cover these concepts in my page about Interrupts however to avoid giving a link-only answer I'll repeat the relevant bits.



What are "volatile" variables?

Variables shared between ISR functions and normal functions should be declared volatile. This tells the compiler that such variables might change at any time, and thus the compiler must reload the variable whenever you reference it, rather than relying upon a copy it might have in a processor register.

For example:

volatile boolean flag;

// Interrupt Service Routine (ISR)
void isr ()
{
 flag = true;
}  // end of isr

void setup ()
{
  attachInterrupt (0, isr, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  if (flag)
    {
    // interrupt has occurred
    }
}  // end of loop




Critical sections ... accessing volatile variables

There are some subtle issues regarding variables which are shared between interrupt service routines (ISRs) and the main code (that is, the code not in an ISR).

Since an ISR can fire at any time when interrupts are enabled, you need to be cautious about accessing such shared variables, as they may be being updated at the very moment you access them.



First ... when do you use "volatile" variables?

A variable should only be marked volatile if it is used both inside an ISR, and outside one.


Variables only used outside an ISR should not be volatile.
Variables only used inside an ISR should not be volatile.
Variables used both inside and outside an ISR should be volatile.


eg.

volatile int counter;


Marking a variable as volatile tells the compiler to not "cache" the variable's contents into a processor register, but always read it from memory, when needed. This may slow down processing, which is why you don't just make every variable volatile, when not needed.



Atomic access

Consider this code:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }

void setup ()
  {
  }

void loop () 
  {
  count++;
  }


The code generated for count++ (add 1 to count) is this:

 14c:   80 91 00 02     lds r24, 0x0200
 150:   8f 5f           subi    r24, 0xFF     &lt;&lt;---- problem if interrupt occurs before this is executed
 152:   80 93 00 02     sts 0x0200, r24   &lt;&lt;---- problem if interrupt occurs before this is executed


(Note that it adds 1 by subtracting -1)

There are two danger points here, as marked. If the interrupt fires after the lds (load register 24 with the variable count), but before the sts (store back into the variable count) then the variable might be changed by the ISR (TIMER1_OVF_vect), however this change is now lost, because the variable in the register was used instead.

We need to protect the shared variable by turning off interrupts briefly, like this:

volatile byte count;

ISR (TIMER1_OVF_vect)
  {
  count = 10;
  }  // end of TIMER1_OVF_vect

void setup ()
  {
  }  // end of setup

void loop () 
  {
  noInterrupts ();
  count++;
  interrupts ();
  } // end of loop


Now the update of count is done "atomically" ... that is, it cannot be interrupted.



Multi-byte variables

Let's make count a 2-byte variable, and see the other problem:

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop () 
  {
  if (count &gt; 20)
     digitalWrite (13, HIGH);
  } // end of loop


OK, we are not changing count any more, so is there still a problem? Sadly, yes. Let's look at the generated code for the "if" statement:

 172:   80 91 10 02     lds r24, 0x0210
 176:   90 91 11 02     lds r25, 0x0211  &lt;&lt;---- problem if interrupt occurs before this is executed
 17a:   45 97           sbiw    r24, 0x15   
 17c:   50 f0           brcs    .+20        


Imagine that count was 0xFFFF and was about to "wrap around" back to zero. We load 0xFF into one register, but before we load the second 0xFF the variable changes to 0x00. Now we think count is 0x00FF which is neither the value it had before (0xFFFF) or now (0x0000).

So again we have to "protect" the access to the shared variable, like this:

void loop () 
  {
  noInterrupts ();
  if (count &gt; 20)
     digitalWrite (13, HIGH);
  interrupts ();
  }  // end of loop




What if you are not sure interrupts are on or off?

There is a final "gotcha" here. What if interrupts might be off already? Then turning them back on afterwards is a Bad Idea.

In that case you need to save the processor status register like this:

unsigned int getCount ()
  {
  unsigned int c;
  byte oldSREG = SREG;   // remember if interrupts are on or off

  noInterrupts ();   // turn interrupts off
  c = count;         // access the shared variable
  SREG = oldSREG;    // turn interrupts back on, if they were on before

  return c;          // return copy of shared variable
  }  // end of getCount


This "safe" code saves the current status of interrupts, turns them off (they may already be off), gets the shared variable into a temporary variable, turns interrupts back on - if they were on when we entered the function - and then returns the copy of the shared variable.



Summary

Code may "appear to work" even if you don't take the above precautions. That is because the chances of the interrupt occurring at the exact wrong moment is fairly low (maybe 1 in 100, depending on the code size). But sooner or later it will happen at the wrong moment, and your code will either crash, or occasionally return the wrong results.

So for reliable code, pay attention to protecting access to shared variables.