Memory (program) usage: "byte var[73]" vs "char var[73]"

Hi, I’m pretty new and just started coding on my own for ‘fun’.

For a project I’m working on, I need a string of Passwords. Those passwords can be changed, so are stored in the EEPROM. To reset all passwords I’ve created a function with the “factory new” passwords hard coded in. There are also other ‘numeric’ values that need to be able to be restored in EEPROM in a similar fashion.

Now I’m running out of flash memory (?) -program memory- and I’m trying to optimize about everything I can.
So I changed my password characters from a “char var[ x ]” type to a “byte var[ x ]” type and was pretty surprised to see that the latter uses way more program memory.

I just can’t figure out why. It was my understanding that a char occupies the same amount of memory as a byte.

So can anyone tell me why this happens? Understanding why is more useful than just knowing it works like that.
Also, any useful tips to reduce memory usage? I changed all my digital.read/write and PinModes with direct port manipulation, use F() macro’s, got my variables right (use byte whenever possible)… Next useful thing is for me to ditch the servo library (700 bytes) since I only use the servo once in the code and maybe ditch my keypad library as well.

This example uses 1648 bytes:

#include <EEPROM.h>
#include <Wire.h>




void setup() {}

void loop() {

char PassWord[73]= "270C2516#A8A1068*DA##733173900DBCBAA9501033550530641838931873391CD*#0923";

for (byte x = 0; x < 72; x++) { EEPROM.update(x ,PassWord[x] );}
}

And this one uses 1788 bytes:

#include <EEPROM.h>
#include <Wire.h>




void setup() {}

void loop() {
byte PassWord[73] = {50 , 55 , 48 , 67 , 50 , 53 , 49 , 54 , 35 , 65 , 56 , 65 , 49 , 48 , 54 , 56 , 42 , 68 , 65 , 35 , 35 , 55 , 51 , 51 , 49 , 55 , 51 , 57 , 48 , 48 , 68 , 66 , 67 , 66 , 65 , 65 , 57 , 53 , 48 , 49 , 48 , 51 , 51 , 53 , 53 , 48 , 53 , 51 , 48 , 54 , 52 , 49 , 56 , 51 , 56 , 57 , 51 , 49 , 56 , 55 , 51 , 51 , 57 , 49 , 67 , 68 , 42 , 35 , 48 , 57 , 50 , 51  };

for (byte x = 0; x < 72; x++) {EEPROM.update(x ,PassWord[x] );}
}

I think I found the culprit. It seems to be that the data itself is of equal length, just that the EEPROM command uses it differently. This behavior also happens with Serial.print commands.
But when you just load the data and only manipulate it (no output to Serial or EEPROM, there is no difference in size:

void setup() {
  // put your setup code here, to run once:
}

void loop() {
//byte PassWord[73] = {50 , 55 , 48 , 67 , 50 , 53 , 49 , 54 , 35 , 65 , 56 , 65 , 49 , 48 , 54 , 56 , 42 , 68 , 65 , 35 , 35 , 55 , 51 , 51 , 49 , 55 , 51 , 57 , 48 , 48 , 68 , 66 , 67 , 66 , 65 , 65 , 57 , 53 , 48 , 49 , 48 , 51 , 51 , 53 , 53 , 48 , 53 , 51 , 48 , 54 , 52 , 49 , 56 , 51 , 56 , 57 , 51 , 49 , 56 , 55 , 51 , 51 , 57 , 49 , 67 , 68 , 42 , 35 , 48 , 57 , 50 , 51  };
char PassWord[73]    = "270C2516#A8A1068*DA##733173900DBCBAA9501033550530641838931873391CD*#0923";
  
for (byte x = 0; x < 72; x++) {
   PassWord[x]=PassWord[x]+1;
  }
}

For data that doesn’t change you are generally better off using FLASH memory (PROGMEM):

#include <EEPROM.h>
// #include <Wire.h>


const char PassWord[73] PROGMEM =
  "270C2516#A8A1068*DA#"
  "#733173900DBCBAA9501"
  "03355053064183893187"
  "3391CD*#0923";


//const byte PassWord[73] PROGMEM =
//{
//  50 , 55 , 48 , 67 , 50 , 53 , 49 , 54 , 35 , 65 ,
//  56 , 65 , 49 , 48 , 54 , 56 , 42 , 68 , 65 , 35 ,
//  35 , 55 , 51 , 51 , 49 , 55 , 51 , 57 , 48 , 48 ,
//  68 , 66 , 67 , 66 , 65 , 65 , 57 , 53 , 48 , 49 ,
//  48 , 51 , 51 , 53 , 53 , 48 , 53 , 51 , 48 , 54 ,
//  52 , 49 , 56 , 51 , 56 , 57 , 51 , 49 , 56 , 55 ,
//  51 , 51 , 57 , 49 , 67 , 68 , 42 , 35 , 48 , 57 ,
//  50 , 51
//};


void setup() {}


void loop()
{
  for (byte x = 0; x < 72; x++)
  {
    EEPROM.update(x , pgm_read_byte(&PassWord[x]) );
  }
}

This version uses 606 bytes of FLASH/PROGMEM and 9 bytes of RAM, regardless of which way the data is initialized.