This is an excellent question to illustrate the amount of string copying and heap operations (malloc/free) going on when using the Arduino String class.
The compiler will generate loop() something like this:
// String literal is stored in program memory. Needs to be copied
// to temporary variable. This is done before main() is called.
static const char temp0 PROGMEM = "def";
static const char temp1;
// The parameter to the call to foo() is actually a temporary String
// variable constructed from the string literal. Storage is allocated
// on the heap and the assigned from the value of the string literal.
// The return value of the call to foo() is a String that is passed
// to Serial.println(). This is also a temporary String variable.
// The String class destructor has to be called for the temporary
// String variable so that the string values on heap are deallocated.
Note that each call to the constructor involves allocating and copying data to the heap.
String foo(String arg1)
String test = "abc";
test = test + arg1;
The function foo() needs to copy strings several times to perform the string concatenation. The operator+ will require an intermediate String copy.
PS: For more details please see the assembly listing below with the calls to String member functions. The compiler reduces inline member functions and reuses temporary local variables. Also the call to foo() is inlined. The member function reserve() is part of the constructor.
1de: 60 df rcall .-320 ; 0xa0 <String::reserve(unsigned int)>
1fc: 50 d2 rcall .+1184 ; 0x69e <strcpy>
212: 46 df rcall .-372 ; 0xa0 <String::reserve(unsigned int)>
230: 36 d2 rcall .+1132 ; 0x69e <strcpy>
248: 62 df rcall .-316 ; 0x10e <String::operator=(String const&)>
26a: 1a df rcall .-460 ; 0xa0 <String::reserve(unsigned int)>
27e: 0f d2 rcall .+1054 ; 0x69e <strcpy>
28e: 3f df rcall .-386 ; 0x10e <String::operator=(String const&)>
294: 60 df rcall .-320 ; 0x156 <String::~String()>
29a: 5d df rcall .-326 ; 0x156 <String::~String()>
2a0: 5a df rcall .-332 ; 0x156 <String::~String()>
Bottom-line is that the String class uses a lot of instruction cycles and memory, and there is a potential risk of heap fragmentation and allocation failure.