PROGMEM -- weird result

Why does the first of the three loops below work while the second one doesn't?

const int16_t values[] PROGMEM = {-1222, -1208, -1194, -1179};

void setup()
{
  Serial.begin(115200);
  Serial.println("\nHello\n");
  
  for (uint8_t i=0; i<3; i++)
    Serial.println(values[i]);

  Serial.println();

  for (uint8_t i=0; i<4; i++) {
    Serial.println(values[i]);
  }

  Serial.println();

  for (uint8_t i=0; i<4; i++) {
    Serial.println(int16_t(pgm_read_word(values + i)));
  }

}

void loop() {}

output:

Hello

-1222
-1208
-1194

0
2
0
1

-1222
-1208
-1194
-1179

jboyton:
Why does the first of the three loops below work while the second one doesn't?

If you write wrong code, there is no guarantee that the result must be wrong.
Sometimes you might do wrong, but the result is correct by by accident.

It's the same as with a mechanical clock that stopped ticking: Twice per day the clock shows the correct time. So a clock standing still is a perfect clock, two times per day.

The difference in your code might be in the compiler optimizations.

My guess is, that the shorter loop is "rolled out" during the code optimization, so actually no for-loop is executed, but that code:

  Serial.println(values[0]);
  Serial.println(values[1]);
  Serial.println(values[2]);

And the next optimization is maybe, that the compiler knows at compile time then which is the value and actually executes the compile time optimized code:

Serial.println(-1222);
Serial.println(-1208);
Serial.println(-1194);

But if the loop is to be executed more often, the loop is not rolled out, but done as a loop with increasing index just as you did the programming. And in case the variables are not read by the compiler during compile time and inserted into the (optimized) code, but read at runtime, then the code doesn't know about the variables to be in PROGMEM while you missed to use the pgm_read_word() function.

Thanks. That was a very good explanation.