Function changing global variable not addressed in the function.

I have a function that is changing a global variable and I can't figure out why.

My project sketch is over 300 lines, so I extracted just the function in question into a test sketch, below.

Here's the background. I am accumulating the digits dialed on an old rotary-dial phone into the global: dialedDigits. At some point in the loop, I want to display the number, formatted for human reading. To do this, I use strcpy() to copy the dialedDigits to phoneNumber then call the function: formatPhone()

Question: Why is formatPhone() changing dialedDigits?

char dialedDigits[20];     //This contains the digits dialed (2125551212)
char phoneNumber[20];      //This contains the formatted phone number (212-555-1212)


// ========== setup ==========
void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  strcpy(dialedDigits, "12125551212");

  Serial.print(F("dialedDigits= "));
  Serial.println(dialedDigits);
  Serial.println();

  //Format the dialed number into human-readable. (I.E., 1-212-555-1212)
  strcpy(phoneNumber, dialedDigits);
  Serial.println(formatPhone());

}


// ========== loop ==========
void loop()
{
}



// ========== formatPhone() ==========
char * formatPhone() {
  //--- Function to parse the phone number -----
  //Enter with the phone number as digits in the global: phoneNumber
  //Exit with the formatted phone number in the same global.

  Serial.print(F("dialedDigits on entry to formatPhone()"));
  Serial.println(dialedDigits );
  Serial.print(F("phoneNumber on entry to formatPhone()"));
  Serial.println(phoneNumber );

  char str2[40];
  byte size = strlen(phoneNumber);

  switch (size) {
    case 7:  // 555-1212
      sprintf(str2, "%.3s-%.4s", phoneNumber, phoneNumber + 3);
      strncpy (phoneNumber, str2, sizeof(phoneNumber) + 4);
      break;

    case 10:  // 212-555-1212
      sprintf(str2, "1-%.3s-%.3s-%.4s", phoneNumber, phoneNumber + 3, phoneNumber + 6);
      strncpy (phoneNumber, str2, sizeof(phoneNumber) + 4);
      break;

    case 11:  // 1-212-555-1212
      sprintf(str2, "1-%.3s-%.3s-%.4s", phoneNumber + 1, phoneNumber + 4, phoneNumber + 7);
      strncpy (phoneNumber, str2, sizeof(phoneNumber) + 4);
      break;

    default:
      // if nothing matches, do nothing
      // leave the phoneNumber unchanged
      break;
  }

  Serial.println();
  Serial.print(F("dialedDigits on exit from formatPhone()"));
  Serial.println(dialedDigits );
  Serial.print(F("phoneNumber on exit from formatPhone()"));
  Serial.println(phoneNumber );


  return phoneNumber;
}

This is one of several cases in the code, where you write to memory you don't own. When you misuse strncpy() like this, the effects are not predictable.

      strncpy (phoneNumber, str2, sizeof(phoneNumber) + 4);

I'm not following you. Both strings are global, so why doesn't a function have full access to it?
Are you suggesting that I copy to a local string before building the final string that gets returned?

The line I quoted writes 24 bytes to phoneNumber, which was declared to be of length 20 bytes.

strncpy() is intended to PREVENT you from doing that, but you need to use it correctly. It would be appropriate to use sizeof(phoneNumber) as the last argument.

strlcpy is a better choice than strncpy.
With strncpy, the "destination is padded with zeros until a total of num characters have been written to it" (strncpy - C++ Reference).
So all the 24 bytes are always written. That is indeed 24 bytes as jremington is trying to tell you. That is 20 bytes of the variable and 4 bytes you don't own.

OK, thanks. I see that I am overrunning phoneNumber, which is likely impacting dialedDigits.
(Overruns can be so insidious).

I also need to remember the difference from sizeof() and strlen().

Here's my fixed function code. At least I think it's fixed because it works....

char * formatPhone() {
  //--- Function to parse the phone number -----
  //Enter with the phone number as digits in the global: phoneNumber
  //Exit with the formatted phone number in the same global.

  Serial.print(F("dialedDigits on entry to formatPhone()"));
  Serial.println(dialedDigits );
  Serial.print(F("phoneNumber on entry to formatPhone()"));
  Serial.println(phoneNumber );

  char str2[15];
  byte size = strlen(phoneNumber);

  switch (size) {
    case 7:  // 555-1212
      sprintf(str2, "%.3s-%.4s", phoneNumber, phoneNumber + 3);
      strncpy (phoneNumber, str2, strlen(str2));
      break;

    case 10:  // 212-555-1212
      sprintf(str2, "1-%.3s-%.3s-%.4s", phoneNumber, phoneNumber + 3, phoneNumber + 6);
      strncpy (phoneNumber, str2, strlen(str2));
      break;

    case 11:  // 1-212-555-1212
      sprintf(str2, "1-%.3s-%.3s-%.4s", phoneNumber + 1, phoneNumber + 4, phoneNumber + 7);
      strncpy (phoneNumber, str2, strlen(str2));
      break;

    default:
      // if nothing matches, do nothing
      // leave the phoneNumber unchanged
      break;
  }

Why not just?

      sprintf(phoneNumber, "%.3s-%.4s", phoneNumber, phoneNumber + 3);

"Why not just?,,"

Because the dialed number can be anything from 1 to 11-digits long.
The phone is part of an elaborate game, like an escape room. The players will receive a phone call instructing them with the goal of the game and their first clue. As they find clues, they will dial their guess into the phone, which will respond with either a prank or the next clue.

The dialed number is sent via MQTT to Node-Red running on a Raspberry Pi which will control the flow of the game. The formatted phone number is for the convenience of the person monitoring the game. Node-Red will work with the raw unformatted number.

This is probably my most complex project yet. I enjoy learning and this is my first project deliberately avoiding the String class- and that is quite a challenge. The dialer and pubsub client are on a Wemos D1 mini, and the audio is on an Adafruit Audio Shield on an Arduino Uno R3. Communications between the two boards is using I2C.