It shouldn't need all those variations, Paul, if it doesn't use them. However I believe I see the problem ...
First for a fair test the "shorter" one should read:
void setup()
{
Serial.begin(9600);
Serial.print("start\r\n");
}
void loop(){}
That's because println (as you see above) outputs \r and \n.
Binary sketch size: 1480 bytes (of a 32256 byte maximum)
Now as for the rest, there is an implementation bug, namely this:
void Print::println(void)
{
print('\r');
print('\n');
}
That should read:
void Print::println(void)
{
write('\r');
write('\n');
}
Why? Because it is calling:
void Print::print(char c, int base)
Which casts c to long and then calls:
void Print::print(long n, int base)
Which calls:
void Print::printNumber(unsigned long n, uint8_t base)
A whole lot of effort (and time) to convert a newline into a decimal number, base 10, and then cast it back to what it was, and then print it.
Compare to this:
void myprintln ()
{
Serial.write ('\r');
Serial.write ('\n');
}
void myprintln (const char c[])
{
Serial.print (c);
myprintln();
}
void setup()
{
Serial.begin(9600);
myprintln("start");
}
void loop(){}
Binary sketch size: 1512 bytes (of a 32256 byte maximum)
Only 32 more bytes now, by using a version of println that writes rather than prints.
So I seriously think the implementors of the Print library should change println to write, not print.
Even better, combine into a single write:
void myprintln ()
{
Serial.write ("\r\n");
}
void myprintln (const char c[])
{
Serial.print (c);
myprintln();
}
void setup()
{
Serial.begin(9600);
myprintln("start");
}
void loop(){}
Now size is:
Binary sketch size: 1500 bytes (of a 32256 byte maximum)
Only 20 more bytes. That's not too bad, after all we got another function out of it (println) that we didn't use before.