String saved to PROGMEM also saved to RAM?

It's my understanding that a char array declared with the PROGMEM keyword is supposed to be saved only to (flash) ROM, not (S)RAM. Since the Arduino has no way of differentiating a pointer to ROM from a pointer to RAM, we have to use the memcpy_P() functions to get data out of program memory. memcpy() can't access ROM, and a variable declared with PROGMEM isn't supposed to be in RAM at all; yet I've found that both memcpy() and memcpy_P() can be used to retrieve a PROGMEM'd string later on. What's going on here, and how can I place data specifically in ROM or RAM with certainty?

Example sketch:

/* where PROGMEM is in the declaration is irrelevant */
//PROGMEM const char string[] = "This string is saved in ROM";
//const PROGMEM char string[] = "This string is saved in ROM";
  const char PROGMEM string[] = "This string is saved in ROM";
//const char string[] PROGMEM = "This string is saved in ROM";

void setup() {
	Serial.begin(9600);
	while (!Serial); // let Serial finish initializing

	char buffer[64];

	// read string from RAM
	memcpy(buffer, string, sizeof(string));
	Serial.println(buffer);

	// read string from ROM
	memcpy_P(buffer, string, sizeof(string));
	Serial.println(buffer);
}

void loop() {}

Serial output:

This string is saved in ROM
This string is saved in ROM

If I remember correctly memcpy used with (AVR-)GCC is quite smart and will likely notice and take into account the segment in which the data is stored, hence it will work (in the same way when you use memcpy for type punning it will likely not duplicate nor move data)

if you write you own basic memcpy(); then it will fail (in the same way as if you try to print string directly)

const char PROGMEM string[] = "IN FLASH";

void * myMemcpy (void *dest, const void *src, size_t len) {
  char *d = static_cast<char*>(dest);
  const char *s = static_cast<const char*>(src);
  while (len--) *d++ = *s++;
  return dest;
}

void dump(const void *src, size_t len) {
  const uint8_t *s = static_cast<const uint8_t *>(src);
  while (len--) {
    Serial.print("0x"); Serial.print(*s++, HEX);
    Serial.write(' ');
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  while (!Serial); 

  char buffer[64];

  // read string from RAM with custom function using pointers
  myMemcpy(buffer, string, sizeof(string));
  Serial.print("Hex dump for Buffer : ");  dump(buffer, sizeof(string));
  Serial.print("Buffer : "); Serial.println(buffer);

  // read string from RAM with memcpy
  memcpy(buffer, string, sizeof(string));
  Serial.print("Hex dump for Buffer : ");  dump(buffer, sizeof(string));
  Serial.print("Buffer : "); Serial.println(buffer);

  // read string from Flash correctly 
  memcpy_P(buffer, string, sizeof(string));
  Serial.print("Hex dump for Buffer : ");  dump(buffer, sizeof(string));
  Serial.print("Buffer : ");  Serial.println(buffer);
}

void loop() {}

Using memcpy_P ensures that your code is portable across different microcontroller platforms and compilers so you don't have to rely on this working or not and you provide clarity and Intent for whoever reads the code.

The compiler is probably optimizing your code, which does tend to cause confusion. To test this, turn code optimization off.

You are correct. That also explains why the RAM/ROM usage changes in the expected fashion if I remove the PROGMEM tag. Thank you :slight_smile:

You really do need to be careful of the compiler optimizations. I have seen questions where someone has declared an array in PROGMEM, then in test code print out a single element of the array, without using the specific commands needed to access PROGMEM. The test code works, so they assume the coding is correct, but as soon as they expand the use of the array, or try to print an element that is not known at compile time, the code fails.

optimization could possibly be also part of the puzzle but I doubt so in the code I posted as I access the progmem address both through memcpy and directly (and memcpy works and not the direct addressing)

EDIT: but the optimiser is smart... so :wink:

Never turn off optimization. To check what the compiler is doing, disassemble the binary with avr-objdump...

I do it all the time, when I'm curious, AND look at the disassembly output. The result is quite informative.

But thanks for your concern!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.