Angus Comber
Angus Comber

Reputation: 9708

Returning local variable by copy - how does it work

Given the sample program below, retlocal1 works while retlocal2 doesn't. I know the rule about not returning a reference or pointer to a local variable but I was wondering how it works.

When retlocal1 returns it copies its value to EAX? But EAX is a register with enough space to hold an integer? So how does EAX hold the entire copy of the std::string (which could of course be a long long string).

There must be something going on under the hood that I don't understand?

This example is C++, but I assume C works exactly in the same way?

#include <string>

std::string retlocal1() {
   std::string s;
   s.append(3, 'A');
   return s;
}

std::string& retlocal2() {
   std::string s;
   s.append(3, 'A');
   return s;
}

int main(int argc, char* argv[]){

   std::string d = retlocal1();
   std::string e = retlocal2();
   return 0;
}

Upvotes: 5

Views: 935

Answers (3)

utnapistim
utnapistim

Reputation: 27385

There must be something going on under the hood that I don't understand?

There is.

retlocal2 returns a reference to a local object, which is undefined behavior (that is, the object goes out of scope and is destroyed, then you return an invalid reference to the calling code).

retlocal1 returns a movable temporary object (an r-value reference).

You would need to ask more specific questions if you want a more precise answer (not sure what you don't understand :) ).

Upvotes: 0

Mike Seymour
Mike Seymour

Reputation: 254751

The calling convention will specify how to return values that are too large for a single register. Smallish types might be returned in multiple registers; large types by passing a "hidden" pointer argument to the function, specifying where the returned value should be placed.

If you want to know all the gory details, Wikipedia is a good starting point.

Upvotes: 6

When retlocal1 returns it copies its value to EAX? But EAX is a register with enough space to hold an integer? So how does EAX hold the entire copy of the std::string (which could of course be a long long string).

This is not correct. You should check the ABI for your platform, but the most common approach is that the calling convention for functions returning large (larger than a register) objects transforms the function into a function that takes an implicit pointer to the returned object. The caller allocates the space for the std::string, and the return statement is transformed into copy construction into that location:

// Transformed function (with no NRVO)
void retlocal(std::string *ret) {
   std::string s; s.append(3, 'A');
   new (ret) std::string(s);
   return;
}

The compiler for that particular case will apply Named Return Value Optimization, which will remove the object s and construct in place of the returned object, avoiding the copy:

void retlocal(std::string *ret) {
   new (ret) std::string();
   ret->append(3,'A');
   return;
}

Upvotes: 2

Related Questions