A note about trunctation - but why?

Take this minimal exa ple:

static const char FWVER[] PROGMEM = {"The quick brown fox jumped over the lazy dog."};
char copy[48];

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(500);
  memset(copy, '\0', 48);
  strncpy_P(copy, FWVER, strlen(FWVER));
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println(copy);
  delay(1000);
}

When I compile for an AVR board such as the UNO there is no problem. However when I compile for the Pico RP2040, I get:

In file included from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/../../../ArduinoCore-API/api/String.h:33,
                 from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/String.h:2,
                 from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/../../../ArduinoCore-API/api/IPAddress.h:23,
                 from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/../../../ArduinoCore-API/api/ArduinoAPI.h:30,
                 from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/ArduinoAPI.h:2,
                 from /home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/Arduino.h:28,
                 from /home/johnc/.cache/arduino/sketches/4E1F0460278FE026E8F8598D1F5C9E18/sketch/sketch_jul3a.ino.cpp:1:
/tmp/.arduinoIDE-unsaved202563-13553-1ewk6mf.hvc3/sketch_jul3a/sketch_jul3a.ino: In function 'void setup()':
/home/johnc/.arduino15/packages/rp2040/hardware/rp2040/4.6.0/cores/rp2040/api/../../../ArduinoCore-API/api/deprecated-avr-comp/avr/pgmspace.h:76:37: warning: 'char* strncpy(char*, const char*, size_t)' output truncated before terminating nul copying 45 bytes from a string of the same length [-Wstringop-truncation]
   76 | #define strncpy_P(s1, s2, n) strncpy((s1), (s2), (n))
      |                              ~~~~~~~^~~~~~~~~~~~~~~~~
/tmp/.arduinoIDE-unsaved202563-13553-1ewk6mf.hvc3/sketch_jul3a/sketch_jul3a.ino:7:3: note: in expansion of macro 'strncpy_P'
    7 |   strncpy_P(copy, FWVER, strlen(FWVER));
      |   ^~~~~~~~~
Sketch uses 57832 bytes (2%) of program storage space. Maximum is 2093056 bytes.
Global variables use 9468 bytes (3%) of dynamic memory, leaving 252676 bytes for local variables. Maximum is 262144 bytes.

Its only a "note" but I am trying to understand why its warning about a truncation? Any why on the Pico but not on AVR boards? The entire line does actually get printed.

Additional question: looking at that macro, do I even need to use strncpy_P on the Pico? I am sure that when I omitted the _P on AVR boards that did result in gobledegook, but the Pico does print the line correctly.

I am using the Earle Pillhower board package, currently at version 4.6.0.

stringop-truncation was added in gcc 8.0

AVR is using gcc 7.3.0

Pico is using gcc 14.2.0

Is an AVR only construct - it is meaningless on an ARM processor like the Pico. There are some kludges in the build process for backward compatibility. It would be better to just delete it in ARM code (or replace it with const to indicate that it in not modified).

Ok, well that explains why it happens on one platform but not the other. However, in my example, the sequence of characters is shorter than the size of the destination array. The char array is also pre-filled with null, so there shouldn't be any chance of anything getting truncated or not having a missing terminating null in the destination. In this case I used strlen() to determine the length of the source, but I got the same warning also when I used a literal number of characters.

You don't need the strncpy_P on an AVR board either, print will work directly from PROGMEM if you cast the char* to __FlashStringHelper*.

static const char FWVER[] PROGMEM = {"The quick brown fox jumped over the lazy dog."};

void setup() {
  Serial.begin(115200);
  delay(500);
}

void loop() {
  Serial.println((__FlashStringHelper*)FWVER);
  delay(1000);
}

That makes sense. I will probably add conditional compile directives for the Pico and alternative code. Make me wonder if its the same for the ESP32.

That's an interesting thought.

strncpy_P expects the number of characters to copy. This need to include the string termination.

strlen returns the length of the string without the termination.

Basically you want to copy the string without the termination.

Probably you want something like this:
strncpy_P(copy, FWVER, sizeof(copy));

You can’t do strlen() on a PROGMEM string.

Completely missed that, it should be strlen_P() for a string in PROGMEM.

Printing it out, both functions give the same value, making me question if the compiler is evaluating the value at compile time.

  strncpy_P(copy, FWVER, strlen(FWVER));

the strncpy() functions are "safe" string copies that will prevent copying more bytes than there are in the destination, IFF you use it correctly. The "length" passed in should be the max length of the destination, and NOT the length of the source.

THAT would be correct.

strncpy() also fills in the extra bytes in the destination with zeros, up to the length specified, which surprises a lot of people who were expecting just the "safe" aspect. Tt's designed for putting a null terminated string into a fixed-width field "cleanly."
Use strlcpy() if you only want the length-limiting safety.

PROGMEM is ignored on ESP32, it’s an empty macro

Well, if I set this to a numeric value that is the exact length of the destination, the warning disappears. I guess sizeof(copy) would accomplish the same thing. I stand corrected. From the C++ documentation I got the impression that its related to the number of characters that one wants copying and therefore associated with the source. I imagine its going to stop copying anyway when it hits the null terminator, but if one thinks of it as a limit i.e. mustn't copy more characters than can fit into the size of the destination then that makes sense.

That's a rather nice feature. Yet I have seen posts (elsewhere) suggesting that strlcpy should be used instead. Curious.

Having said all of that, david_2018's solution turns out to be the simplest and easiest. No messing about with strncpy or strncpy_P required and it works, so that is the solution I am running with.

There is one final question I will ask. If PROGMEM is not supported on the Pico or ESP, then how does Serial.println(F("Hello world!")); or anything else using the F() macros get stored? Does the string just get loaded and held in runtime memory?

On most non-AVR processors the compiler automatically stores const data in flash memory, since no special instructions are needed for accessing flash memory vs ram. The AVR processors that require the use of PROGMEM have the flash and ram in different address spaces, requiring different instructions to access each.