strpbrk() works, strpbrk_P does not - Why? [SOLVED]

I'm attempting a variation to parse/tokenize a progmem string as in this forum post. I've been puttering with this and making no headway.

strpbrk() works and produces the correct output:

// tokenize string in progmem - search delimiter with strpbrk()

/* Description: Locate character in program space string.
  char * strpbrk  ( const char *  s,
                    const char *  accept
                  )

  The strpbrk() function locates the first occurrence in the string s of any
  of the characters in the flash string accept. This function is similar to
  strpbrk() except that accept is a pointer to a string in program space.

  Returns
  The strpbrk() function returns a pointer to the character in s that matches
  one of the characters in accept, or NULL if no such character is found.
  The terminating zero is not considered as a part of string: if one or
  both args are empty, the result will NULL.

*/
// https://forum.arduino.cc/index.php?topic=663437.0

char testData[]  = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const char delimiters[] = ":-";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

// declare three pointers to the progmem string
char* nextSegmentPtr = testData; // points to first character of segment
char* delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
char* endOfData = testData + strlen(testData); // points to last character in string

void setup() {
  Serial.begin(115200);
//  Serial.println(__FILE__);
  byte segmentLen; // number of characters in current segment
  while (1) {
    delimiterPtr = strpbrk(nextSegmentPtr, delimiters); // Locate target character in progmem string.
    segmentLen = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      segmentLen = endOfData - nextSegmentPtr;
    }
    if (segmentLen <= segmentSize) {
      memcpy(scratchPad, nextSegmentPtr, segmentLen);
      scratchPad[segmentLen] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("segmentLen "));
      Serial.print(segmentLen);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(segmentLen);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }
    nextSegmentPtr = nextSegmentPtr + segmentLen + 1;
  }// end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {

}

Output:

segmentLen 2 : 01
segmentLen 3 : 234
segmentLen 2 : 56
segmentLen 2 : 78
segmentLen 2 : 90
segmentLen 2 : ab
segmentLen 2 : cd
segmentLen 3 : efg
segmentLen 2 : hi
segmentLen 4 : jklm
segmentLen 5 : zztop

*** done ***

The progmem version, strpbrk_P, although compiling and running without error produces only what is shown below.

// tokenize string in progmem - search delimiter with strpbrk_P()

/* Description: Locate character in program space string.
  char * strpbrk_P  ( const char *  s,
                    const char *  accept
                    )

  The strpbrk_P() function locates the first occurrence in the string s of any
  of the characters in the flash string accept. This function is similar to
  strpbrk() except that accept is a pointer to a string in program space.

  Returns
  The strpbrk_P() function returns a pointer to the character in s that matches
  one of the characters in accept, or NULL if no such character is found.
  The terminating zero is not considered as a part of string: if one or
  both args are empty, the result will NULL.
*/
// https://forum.arduino.cc/index.php?topic=663437.0
//--------------------------------------------

const char testData[] PROGMEM = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const char delimiters[] PROGMEM = ":-";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

// declare three pointers to the progmem string
PGM_P nextSegmentPtr = testData; // points to first character of segment
PGM_P delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
PGM_P endOfData = testData + strlen(testData); // points to last character in string

void setup() {
  Serial.begin(115200);
//  Serial.println(__FILE__);
  byte segmentLen; // number of characters in current segment
  while (1) {
    delimiterPtr = strpbrk_P(nextSegmentPtr, delimiters); // Locate target character in progmem string.
    segmentLen = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      segmentLen = endOfData - nextSegmentPtr;
    }
    if (segmentLen <= segmentSize) {
      memcpy_P(scratchPad, nextSegmentPtr, segmentLen);
      scratchPad[segmentLen] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("segmentLen "));
      Serial.print(segmentLen);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(segmentLen);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }
    nextSegmentPtr = nextSegmentPtr + segmentLen + 1;
  }// end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {

}

segment len 39 skipped

*** done ***

From where I sit the programs differ only in the parts that address progmem. I have yet another nearly identical version which uses strchr_P to do the same thing as the strpbrk() version and it does produce correct output. It's not here 'cuz it put me over the posting size limit. I can add it in another post if necessary.

Anyhow, I think it's a forest and trees situation.

Open my eyes?

don't put the delimiters in progmem

EDIT for future readers: only the second parameter of strpbrk_P should be in progmem

The searched string has to reside in RAM.

const char testData[] PROGMEM = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";

PGM_P nextSegmentPtr = testData; // points to first character of segment

Juraj:
don't put the delimiters in progmem

I tried these:

const char delimiters[] PROGMEM = ":-";
const char delimiters[] PROGMEM = ":-";

and also

delimiterPtr = strpbrk_P(nextSegmentPtr, ":-");

None of these made any difference.

Whandall:
The searched string has to reside in RAM.

OK, here's the strchr() version, which does work.

// tokenize string in progmem - search delimiter with strchr_P()

// Works well.  However, it only detects one delimiter character.
// Implemented with function strpbrk() would allow multiple
// delimiters, if needed/desired.  Uses about 40 fewer bytes flash
// than preliminary strpbrk version.

/* Locate character in program space string.

  >  const char * strchr_P  ( const char *  s,
  >                           int   val
  >                         )

  The strchr_P() function locates the first occurrence of val (converted to a char)
  in the string pointed to by s in program space. The terminating null character is
  considered to be part of the string.

  The strchr_P() function is similar to strchr() except that s is a pointer to a
  string in program space.

  Returns
  The strchr_P() function returns a pointer to the matched character or NULL if the character is not found.
*/
// https://forum.arduino.cc/index.php?topic=663437.0
//--------------------------------------------
// some test strings
//const char testData[] PROGMEM = "abc:12345:67890:hello world:2121:344255:00000";
//const char testData[] PROGMEM = "12:34:56:78:90:11:22:33:44:55:2121:344255:00000";
//const char testData[] PROGMEM = "xy:aaa:bbbbb:ccccccccc:567:eeeeeeee:ffff";
//const char testData[]

/*  VERY long string.  Comment/uncomment together  */
//const char testData[] PROGMEM = "01:234:56:7890:ab:cd:efg:hi:xy:aaa:bbbbb:ccccccccc:567:eeeeeeee:" //
//                                "ffffg:21:43:6576:78111:904:11:2qwerty2:33:##44:55:21212:344255:00000:12345:67890:hello world:2121:344255:00000";
//const char testData[] PROGMEM = "ab:cd:efg:hi:xy:aaa:bbbbb";
const char testData[] PROGMEM = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

// declare three pointers to the progmem string
PGM_P nextSegmentPtr = testData; // points to first character of segment
PGM_P delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
PGM_P endOfData = testData + strlen(testData); // points to last character in string

void setup() {
  Serial.begin(115200);
//Serial.println(__FILE__);
  byte len; // number of characters in current segment
  while (1) {
    delimiterPtr = strchr_P(nextSegmentPtr, ':'); // Locate target character in progmem string.
    len = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      len = endOfData - nextSegmentPtr;
    }
    if (len <= segmentSize) {
      memcpy_P(scratchPad, nextSegmentPtr, len);
      scratchPad[len] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("len "));
      Serial.print(len);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(len);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }

    nextSegmentPtr = nextSegmentPtr + len + 1;
  } // end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {

}

I cannot see where testData[] gets transferred to RAM. It *appears *to work correctly direct from flash. Based on @juraj's post I tested this one with the delimiter character as shown and also putting it into char delimiter in PROGMEM. Either works.

??

As strpbrk(_P) modiefies the searched string, it has to be in RAM.

// tokenize string in progmem - search delimiter with strpbrk_P()

/* Description: Locate character in program space string.
  char * strpbrk_P  ( const char *  s,
                    const char *  accept
                    )

  The strpbrk_P() function locates the first occurrence in the string s of any
  of the characters in the flash string accept. This function is similar to
  strpbrk() except that accept is a pointer to a string in program space.

  Returns
  The strpbrk_P() function returns a pointer to the character in s that matches
  one of the characters in accept, or NULL if no such character is found.
  The terminating zero is not considered as a part of string: if one or
  both args are empty, the result will NULL.
*/
// https://forum.arduino.cc/index.php?topic=663437.0
//--------------------------------------------

char testData[] = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const char delimiters[] PROGMEM = ":-";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

char* nextSegmentPtr = testData; // points to first character of segment
char* delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
char* endOfData = testData + strlen(testData); // points to last character in string

void setup() {
  Serial.begin(250000);
//  Serial.println(__FILE__);
  byte segmentLen; // number of characters in current segment
  while (1) {
    delimiterPtr = strpbrk_P(nextSegmentPtr, delimiters); // Locate target character in progmem string.
    segmentLen = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      segmentLen = endOfData - nextSegmentPtr;
    }
    if (segmentLen <= segmentSize) {
      memcpy(scratchPad, nextSegmentPtr, segmentLen);
      scratchPad[segmentLen] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("segmentLen "));
      Serial.print(segmentLen);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(segmentLen);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }
    nextSegmentPtr = nextSegmentPtr + segmentLen + 1;
  }// end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {}
segmentLen 2 : 01
segmentLen 3 : 234
segmentLen 2 : 56
segmentLen 2 : 78
segmentLen 2 : 90
segmentLen 2 : ab
segmentLen 2 : cd
segmentLen 3 : efg
segmentLen 2 : hi
segmentLen 4 : jklm
segmentLen 5 : zztop

*** done ***

Whandall:
As strpbrk(_P) modiefies the searched string, it has to be in RAM.

Where and how is this indicated?

From the documentation:

char * strpbrk_P ( const char * s,
const char * accept
)

The strpbrk_P() function returns a pointer to the character in s that matches
one of the characters in accept, or NULL if no such character is found.
The terminating zero is not considered as a part of string: if one or
both args are empty, the result will NULL.

and;

Locate character in program space string.

const char * strchr_P (const char * s,
int val
)

The strchr_P() function locates the first occurrence of val (converted to a char) in the string pointed to by s in program space. The terminating null character is considered to be part of the string.

The strchr_P() function is similar to strchr() except that s is pointer to a string in program space.

Returns
The strchr_P() function returns a pointer to the matched character or NULL if the character is not found.

Things I don't get.

Both functions have const char* s as the first argument. Both do essentially the same thing, locate a target character and return a pointer to it. How is it they both don't work the same way?

If the string has to be in RAM what's the use of a _P function?

Weeds getting higher.

dougp:
Where and how is this indicated?

Never mind, I mixed up strtok and strpbrk.

But the documentation of strpbrk_P clearly states that only the second pointer resides in PROGMEM.

The strpbrk_P() function locates the first occurrence in the string s of any
of the characters in the flash string accept. This function is similar to
strpbrk() except that accept is a pointer to a string in program space.

You will probably need to find the code for the strpbrk_P function and write your own function to implement it with the string in program memory, its unlikely the writers of the code thought of that as a potential application since you should already know the location of the delimiters at compile time. I really can't see this being a common need either, since the delimiters are taking the same storage space as a terminating null, it would only cost you two bytes per substring to declare each substring separately and create an array of pointers to them. Doing it that way would also have the advantage that the strings would not need to be copied into ram for printing.

(Edit) I'm not where I can test it at the moment, but you can try casting the first argument of strpbrk() to ( __FlashStringHelper * ) to see if it will accept a string in program memory, I seriously doubt it is implemented.

david_2018:
you can try casting the first argument of strpbrk() to ( __FlashStringHelper * ) to see if it will accept a string in program memory, I seriously doubt it is implemented.

Thanks for the suggestion. I tried it. Yields a compiler error.

    delimiterPtr = strpbrk((__FlashStringHelper*)nextSegmentPtr, delimiters);

error: exit status 1
cannot convert '__FlashStringHelper*' to 'const char*' for argument '1' to 'char* strpbrk(const char*, const char*)'

so why would you parse a constant PROGMEM string? store the parts.
other _P functions are like take this constant string and produce some output based on it (strcpy_P, strcat_P, printf_P) or compare some string with this constant string (strcmp_P, strstr_P, strtok_P, strbrk_P). in the second group the second parameter is the constant string from progmem.

If I take your first sketch in post #1, mark delimiters as PROGMEM and add the _P to strpbrk it works for me.

Juraj:
so why would you parse a constant PROGMEM string? store the parts.

I was just trying to develop the idea in the OP's post linked in the first post on this thread. An array of pointers to the data strings would be more straightforward.

arduarn:
If I take your first sketch in post #1, mark delimiters as PROGMEM and add the _P to strpbrk it works for me.

Yes but, testData is still in RAM. That's why it works.

dougp:
Yes but, testData is still in RAM. That's why it works.

As @david_2018 stated, strpbrk_P works as documented. If you don't like it, write your own.

The API designers made decisions based on the most likely use cases. They decided to have most/all the standard functions complemented with _P where possible. strchr_P has a PROGMEM first argument because that's the only argument that can be marked PROGMEM.

arduarn:
If you don't like it, write your own.

Would that entail learning AVR assembler?

dougp:
Would that entail learning AVR assembler?

Not necessarily. Its not really that hard to do, here is your second example from the original post, with a working strpbrk funtcion:

// tokenize string in progmem - search delimiter with strpbrk_P()

/* Description: Locate character in program space string.
  char * strpbrk_P  ( const char *  s,
                    const char *  accept
                    )

  The strpbrk_P() function locates the first occurrence in the string s of any
  of the characters in the flash string accept. This function is similar to
  strpbrk() except that accept is a pointer to a string in program space.

  Returns
  The strpbrk_P() function returns a pointer to the character in s that matches
  one of the characters in accept, or NULL if no such character is found.
  The terminating zero is not considered as a part of string: if one or
  both args are empty, the result will NULL.
*/
// https://forum.arduino.cc/index.php?topic=663437.0
//--------------------------------------------

const char testData[] PROGMEM = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const char delimiters[] PROGMEM = ":-";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

// declare three pointers to the progmem string
PGM_P nextSegmentPtr = testData; // points to first character of segment
PGM_P delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
PGM_P endOfData = testData + strlen(testData); // points to last character in string

//strpbrk implementation with both arguments in PROGMEM
const char * strpbrk_PP(const char* sourceString, const char* delimiters_) {
  char * temp = nullptr;
  while ( (temp == nullptr) && (pgm_read_byte(sourceString) != '\n') ) {
    //check for matching character until a match is found or the end of the source string is reached
    for (uint16_t i = 0; i < strlen_P(delimiters_); i++) {
      if ( pgm_read_byte(delimiters_ + i) == pgm_read_byte(sourceString) ) {
        //check for match between any of the delimiters and the character from the source string
        temp = (char *)sourceString;
      }
    }
    sourceString++; //increment to next character of source string
  }
  return temp; //return either a pointer to the matching character or a null pointer
}

void setup() {
  Serial.begin(115200);
  //  Serial.println(__FILE__);
  byte segmentLen; // number of characters in current segment
  while (1) {
    delimiterPtr = strpbrk_PP(nextSegmentPtr, delimiters); // Locate target character in progmem string.
    segmentLen = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      segmentLen = endOfData - nextSegmentPtr;
    }
    if (segmentLen <= segmentSize) {
      memcpy_P(scratchPad, nextSegmentPtr, segmentLen);
      scratchPad[segmentLen] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("segmentLen "));
      Serial.print(segmentLen);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(segmentLen);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }
    nextSegmentPtr = nextSegmentPtr + segmentLen + 1;
  }// end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {

}

david_2018:
here is your second example from the original post, with a working strpbrk funtcion:

Thanks for the example! Very nice. It's going to take me a while to digest that.

I must say, I am constantly amazed at the inventiveness of folks who know their way around this language.

Karma++

dougp:
Would that entail learning AVR assembler?

Well, as mentioned, no, but it would certainly help if you want the most efficient implementation. Those sorts of functions would be hand optimised to within an inch of their lives.

ocd mode on

david_2018's implementation seems a little buggy and inefficient to me (a C-string ends with a '\0' not a '\n'; unnecessary use of strlen_P; reading the same data from flash more than once; etc.)
So putting my money where my mouth is, here are two implementations (no warranty and only tested with the example sketch, if you're gonna use one the C one is probably safer and easier to fix), one in ASM based off the orginal strpbrk_P function and one in C.

/*
   Original strpbrk_P:

  00000646 <strpbrk_P>:
  646:   dc 01           movw    r26, r24
  648:   99 27           eor     r25, r25
  64a:   8d 91           ld      r24, X+
  64c:   88 23           and     r24, r24
  64e:   41 f0           breq    .+16            ; 0x660 <strpbrk_P+0x1a>
  650:   fb 01           movw    r30, r22
  652:   05 90           lpm     r0, Z+
  654:   08 16           cp      r0, r24
  656:   01 10           cpse    r0, r1
  658:   e1 f7           brne    .-8             ; 0x652 <strpbrk_P+0xc>
  65a:   b9 f7           brne    .-18            ; 0x64a <strpbrk_P+0x4>
  65c:   11 97           sbiw    r26, 0x01       ; 1
  65e:   cd 01           movw    r24, r26
  660:   08 95           ret
*/

PGM_P __attribute__ ((noinline)) my_ASM_strpbrk_P(PGM_P search, PGM_P accept) {
  PGM_P ret;
  char *xreg, *zreg;
  asm volatile(

    "movw     X, %[s]"                      "\n\t"
    "clr      %B[r]"                        "\n\t"

    "nexts:"                                "\n\t"
    "movw     Z, X"                         "\n\t"
    "adiw     X, 1"                         "\n\t"
    "lpm      %A[r], Z"                     "\n\t"
    "and      %A[r], %A[r]"                 "\n\t"
    "breq     finish"                       "\n\t"
    "movw     Z, %[a]"                      "\n\t"

    "nexta:"                                "\n\t"
    "lpm      __tmp_reg__, Z+"              "\n\t"
    "cp       __tmp_reg__, %A[r]"           "\n\t"
    "cpse     __tmp_reg__, __zero_reg__"    "\n\t"
    "brne     nexta"                        "\n\t"
    "brne     nexts"                        "\n\t"
    "sbiw     X, 1"                         "\n\t"
    "movw     %[r], X"                      "\n\t"

    "finish:"                               "\n\t"

    : [r] "=w" (ret), "=x" (xreg), "=z" (zreg)
    : [s] "0" (search), [a] "a" (accept));
  return ret;
}

/*
   my_ASM_strpbrk_P:

  00000452 <_Z16my_ASM_strpbrk_PPKcS0_>:
    : [s] "0" (search), [a] "a" (accept));
  452:   dc 01           movw    r26, r24
  454:   99 27           eor     r25, r25

  00000456 <nexts>:
  456:   fd 01           movw    r30, r26
  458:   11 96           adiw    r26, 0x01       ; 1
  45a:   84 91           lpm     r24, Z
  45c:   88 23           and     r24, r24
  45e:   41 f0           breq    .+16            ; 0x470 <finish>
  460:   fb 01           movw    r30, r22

  00000462 <nexta>:
  462:   05 90           lpm     r0, Z+
  464:   08 16           cp      r0, r24
  466:   01 10           cpse    r0, r1
  468:   e1 f7           brne    .-8             ; 0x462 <nexta>
  46a:   a9 f7           brne    .-22            ; 0x456 <nexts>
  46c:   11 97           sbiw    r26, 0x01       ; 1
  46e:   cd 01           movw    r24, r26

  00000470 <finish>:
  }
  470:   08 95           ret
*/

PGM_P __attribute__ ((noinline)) my_C_strpbrk_P(PGM_P search, PGM_P accept) {
  char s, a;
  while ((s = pgm_read_byte(search))) {
    PGM_P ac = accept;
    while ((a = pgm_read_byte(ac++))) {
      if (s == a) goto finish;
    }
    ++search;
  }
finish:
  return (s) ? search : 0;
}

/*
   my_C_strpbrk_P:

  PGM_P __attribute__ ((noinline)) my_C_strpbrk_P(PGM_P search, PGM_P accept) {
  char s, a;
  while ((s = pgm_read_byte(search))) {
  452:   fc 01           movw    r30, r24
  454:   34 91           lpm     r19, Z
  456:   33 23           and     r19, r19
  458:   51 f0           breq    .+20            ; 0x46e <_Z14my_C_strpbrk_PPKcS0_+0x1c>
  45a:   fb 01           movw    r30, r22
    PGM_P ac = accept;
    while ((a = pgm_read_byte(ac++))) {
  45c:   24 91           lpm     r18, Z
  45e:   22 23           and     r18, r18
  460:   21 f0           breq    .+8             ; 0x46a <_Z14my_C_strpbrk_PPKcS0_+0x18>
  462:   31 96           adiw    r30, 0x01       ; 1
      if (s == a) goto finish;
  464:   32 13           cpse    r19, r18
  466:   fa cf           rjmp    .-12            ; 0x45c <_Z14my_C_strpbrk_PPKcS0_+0xa>
  468:   08 95           ret
    }
    ++search;
  46a:   01 96           adiw    r24, 0x01       ; 1
  46c:   f2 cf           rjmp    .-28            ; 0x452 <_Z14my_C_strpbrk_PPKcS0_>
  }
  finish:
  return (s) ? search : 0;
  46e:   90 e0           ldi     r25, 0x00       ; 0
  470:   80 e0           ldi     r24, 0x00       ; 0
  }
  472:   08 95           ret
*/

// just for test, remove these and noinline attributes
volatile PGM_P dummy_deleteme1 = my_ASM_strpbrk_P("dummy1", "xx");
volatile PGM_P dummy_deleteme2 = my_C_strpbrk_P("dummy2", "xx");
volatile PGM_P dummy_deleteme3 = my_ASM_strpbrk_P("dummy3", "yy");
volatile PGM_P dummy_deleteme4 = my_C_strpbrk_P("dummy4", "yy");

const char testData[] PROGMEM = "01:234:56:78-90:ab:cd:efg:hi-jklm-zztop";
const char delimiters[] PROGMEM = ":-";
const byte segmentSize = 5 ;  // Segments longer than this will be skipped
char scratchPad[segmentSize + 1]; // A buffer to pull the progmem bytes into for printing/processing

// declare three pointers to the progmem string
PGM_P nextSegmentPtr = testData; // points to first character of segment
PGM_P delimiterPtr = testData;   // points to detected delimiter character, or eos NULL
PGM_P endOfData = testData + strlen_P(testData); // points to last character in string

void setup() {
  Serial.begin(115200);
  //  Serial.println(__FILE__);
  byte segmentLen; // number of characters in current segment
  while (1) {
    delimiterPtr = my_C_strpbrk_P(nextSegmentPtr, delimiters); // Locate target character in progmem string.
    segmentLen = delimiterPtr - nextSegmentPtr;
    if (delimiterPtr == nullptr) { // Hit end of string
      segmentLen = endOfData - nextSegmentPtr;
    }
    if (segmentLen <= segmentSize) {
      memcpy_P(scratchPad, nextSegmentPtr, segmentLen);
      scratchPad[segmentLen] = '\0'; // Append terminator to extracted characters.
      Serial.print(F("segmentLen "));
      Serial.print(segmentLen);
      Serial.print(F(" : "));
      Serial.println(scratchPad);
    }
    else {
      Serial.print(F("segment len "));
      Serial.print(segmentLen);
      Serial.println(F( " skipped"));
    }
    if (delimiterPtr == nullptr) { // ----- Exit while loop here -----
      break;
    }
    nextSegmentPtr = nextSegmentPtr + segmentLen + 1;
  }// end while

  Serial.println(F("\n*** done ***"));
} // end setup

void loop() {

}

And just for completeness, here is david_2018's implementation disassembled as well:

000004c6 <_Z10strpbrk_PPPKcS0_>:
 4c6:   0f 93           push    r16
 4c8:   1f 93           push    r17
 4ca:   cf 93           push    r28
 4cc:   df 93           push    r29
 4ce:   ec 01           movw    r28, r24
 4d0:   8b 01           movw    r16, r22
 4d2:   fe 01           movw    r30, r28
 4d4:   84 91           lpm     r24, Z
 4d6:   88 23           and     r24, r24
 4d8:   b1 f0           breq    .+44            ; 0x506 <_Z10strpbrk_PPPKcS0_+0x40>
 4da:   c8 01           movw    r24, r16
 4dc:   02 d2           rcall   .+1028          ; 0x8e2 <__strlen_P>
 4de:   bc 01           movw    r22, r24
 4e0:   30 e0           ldi     r19, 0x00       ; 0
 4e2:   20 e0           ldi     r18, 0x00       ; 0
 4e4:   90 e0           ldi     r25, 0x00       ; 0
 4e6:   80 e0           ldi     r24, 0x00       ; 0
 4e8:   26 17           cp      r18, r22
 4ea:   37 07           cpc     r19, r23
 4ec:   99 f0           breq    .+38            ; 0x514 <_Z10strpbrk_PPPKcS0_+0x4e>
 4ee:   f8 01           movw    r30, r16
 4f0:   e2 0f           add     r30, r18
 4f2:   f3 1f           adc     r31, r19
 4f4:   44 91           lpm     r20, Z
 4f6:   fe 01           movw    r30, r28
 4f8:   54 91           lpm     r21, Z
 4fa:   45 13           cpse    r20, r21
 4fc:   01 c0           rjmp    .+2             ; 0x500 <_Z10strpbrk_PPPKcS0_+0x3a>
 4fe:   ce 01           movw    r24, r28
 500:   2f 5f           subi    r18, 0xFF       ; 255
 502:   3f 4f           sbci    r19, 0xFF       ; 255
 504:   f1 cf           rjmp    .-30            ; 0x4e8 <_Z10strpbrk_PPPKcS0_+0x22>
 506:   90 e0           ldi     r25, 0x00       ; 0
 508:   80 e0           ldi     r24, 0x00       ; 0
 50a:   df 91           pop     r29
 50c:   cf 91           pop     r28
 50e:   1f 91           pop     r17
 510:   0f 91           pop     r16
 512:   08 95           ret

ocd mode off