4.11.2.2 Clobber Operand

The list of clobbered registers is optional; however, if the instruction modifies registers that are not specified as operands, you need to inform the compiler of these changes.

Typically you can arrange the assembly so that you do not need to specify what has been clobbered. Indicating that a register has been clobbered will force the compiler to store their values before and reload them after your assembly instructions and will limit the ability of the compiler to optimize your code.

The following example will perform an atomic increment. It disables the interrupts then increments an 8-bit value pointed to by a pointer variable. Note, that a pointer is used because the incremented value needs to be stored before the interrupts are enabled.

asm volatile(
 "cli" "\n\t"
 "ld r24, %a0" "\n\t"
 "inc r24" "\n\t"
 "st %a0, r24" "\n\t"
 "sei" "\n\t"
 :
 : "e" (ptr)
 : "r24"
);

The compiler might produce the following code for the above:

cli
ld r24, Z
inc r24
st Z, r24
sei

To have this sequence avoid clobbering register r24, make use of the special temporary register __tmp_reg__ defined by the compiler.

asm volatile(
 "cli" "\n\t"
 "ld __tmp_reg__, %a0" "\n\t"
 "inc __tmp_reg__" "\n\t"
 "st %a0, __tmp_reg__" "\n\t"
 "sei" "\n\t"
 :
 : "e" (ptr)
);

The compiler will always reload the temporary register when it is needed.

The above code unconditionally re-enables the interrupts, which may not be desirable. To make the code more versatile, the current status can be stored in a register selected by the compiler.

{
  uint8_t s;
  asm volatile(
  "in %0, __SREG__" "\n\t"
  "cli" "\n\t"
  "ld __tmp_reg__, %a1" "\n\t"
  "inc __tmp_reg__" "\n\t"
  "st %a1, __tmp_reg__" "\n\t"
  "out __SREG__, %0" "\n\t"
  : "=&r" (s)
  : "e" (ptr)
  );
}

The assembler code here modifies the variable, that ptr points to, so the definition of ptr should indicate that its target can change unexpected, using the volatile specifier, for example:

volatile uint8_t *ptr;

The special clobber memory informs the compiler that the assembler code may modify any memory location. It forces the compiler to update all variables for which the contents are currently held in a register before executing the assembler code.

When you use a memory clobber with an assembly instruction, it ensures that all prior accesses to volatile objects are complete before the instruction executes, and that execution of volatile accesses after the instruction have not commenced. However, it does not prevent the compiler from moving non-volatile-related instructions across the barrier created by the memory clobber instruction, as such instructions might be those that enable or disable interrupts.