Why does the MCU go back? (to the previous instruction)

cdebuggingidestm32stm32f0

I don't know why but I see the same odd behaviour sometimes in the debugger environment. Look at this code (function):

static void I2C_TransferConfig(I2C_HandleTypeDef *hi2c,  uint16_t DevAddress, uint8_t Size, uint32_t Mode, uint32_t Request)
{
  uint32_t tmpreg = 0;

  /* Check the parameters */
  assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance));
  assert_param(IS_TRANSFER_MODE(Mode));
  assert_param(IS_TRANSFER_REQUEST(Request));

  /* Get the CR2 register value */
  tmpreg = hi2c->Instance->CR2;

  /* clear tmpreg specific bits */
  tmpreg &= (uint32_t)~((uint32_t)(I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | I2C_CR2_AUTOEND | I2C_CR2_RD_WRN | I2C_CR2_START | I2C_CR2_STOP));

  /* update tmpreg */
  tmpreg |= (uint32_t)(((uint32_t)DevAddress & I2C_CR2_SADD) | (((uint32_t)Size << 16 ) & I2C_CR2_NBYTES) | \
            (uint32_t)Mode | (uint32_t)Request);

  /* update CR2 register */
  hi2c->Instance->CR2 = tmpreg;  
}

And take a look at this picture of code in the debugger environment:

enter image description here

When I run it, at first it goes to line 4057 and then it goes to line 4054! Why?

As you can see, both lines are only several variable + several operands! That's all!

Best Answer

The C standard specifies that reads and writes of volatile variables and calls to library routines are considered "observable operations". A compiler is required to produce an executable which will produce all observable operations in the sequence that the source file directs, but the generated code is change around anything else in the code as it sees fit provided only that all observable operations happen in the same sequence.

For example, if code computes a value, performs an observable operation which does not make use of the value, and then performs one which does, the compiler may decide to perform the first observable operation before computing the value needed for the second. Given code like:

int x = y/z;
if (volatile1)
  volatile2 = x;

if the value x is never used after the above code, and if variables y and z are not volatile, a compiler might legitimately decide that the code would be more efficient as

if (volatile1)
{
  int x = y/z;
  volatile2 = x;
}

since in the situation where volatile1 is zero it takes less time to execute, and if volatile1 is non-zero it doesn't take any longer. Of course, the whole purpose of doing the assignment before testing volatile may have been to minimize the elapsed time between reading volatile1 and writing volatile2, but if that's the case if may be helpful to do something like:

volatile int dummy_volatile;
#define USE_VALUE(x) (dummy_volatile = (int)(x))

and then...

int x = y/z;
USE_VALUE(x);
if (volatile1)
  volatile2 = x;

That would ensure that an observable operation using the computed value would take place before the observable read of volatile1. Some compilers may offer a means of "observably using" a value without having to actually store it anyplace, but I don't know of any standard means of doing that.