2

This question doesn't completely help me to clarify this issue. Say I have the function:

int foo(){
   /* some code */
   return 4;
}

int main(){
   foo();
}

Since I'm returning by value, will a copy of the integer returned by foo() be made? This answer mentions that the compiler will turn the function into a void function. Does that mean the actual function being called will be this one?

void foo(){
   /* some code */
}

This question is related to using a class method as a helper function and an interface function at the same time. For instance, assume the classes Matrix and Vector are defined.

class Force
{
    public:
        Matrix calculate_matrix(Vector & input);
        Vector calculate_vector(Vector & input);
    private:
        Matrix _matrix;
        Vector _vector;
 };

Implementation

Matrix Force::calculate_matrix(Vector & input){
    /* Perform calculations using input and writing to _matrix */
    return _matrix;
}
Vector Force::calculate_vector(Vector & input){
    calculate_matrix(input)
    /* Perform calculations using input and _matrix and writing to _vector */
    return _vector;
}            

Whenever I call Force::calculate_matrix() in Force::calculate_vector(), am I making a copy of _matrix? I need Force::calculate_matrix() as an interface whenever I just need to know the value of _matrix. Would a helper function such as:

void Force::_calculate_matrix(Vector & input){
   /* Perform calculations using input and writing to _matrix */
}

and then use it in Force::calculate_matrix() and Force::calculate_vector() be a better design solution or I can get away with the compiler optimizations mentioned above to avoid the copies of the Force::calculate_matrix() returning value?

Community
  • 1
  • 1
balborian
  • 211
  • 3
  • 5
  • 1
    why not avoid this problem entirely and return `const Matrix &`? – BeyelerStudios Nov 20 '15 at 18:41
  • Sorry I forgot to mention that I have to call `Force::calculate_matrix(Vector & input)` with two different `input` in order to have two different `Matrix` that I will compare. – balborian Nov 20 '15 at 18:46
  • Related/Dupe: [What happens to unused function return values?](http://stackoverflow.com/questions/15096509/what-happens-to-unused-function-return-values) – NathanOliver Nov 20 '15 at 18:50

2 Answers2

3

To answer this question, it is worth revisiting the whole 'returning' from the function thing. It is really interesting once you start digging into it.

First of all, the int example is really irrelevant to the latter question. Stay tuned to know why :)

First thing to remember is that there is no 'returning values' on the assembly level. Instead, the rules which guide how to return a value from a function are defined in what is called ABI protocol. Not so long ago, there were many different ABI protocols in action, but now, thanks someone, most of what we see is so-called AMD64 ABI. In particular, according to it, returning an integer means pushing a value into RAX registry.

So when you ignore an integer return value, your code will simply not read RAX.

It is different when you return an object. As a matter of fact, the object is returned in the place prepared by the caller function (location of this place is passed to the callee). Callee performs all initialization and populates the object with values. Calling code than handles the 'space' as approriate.

Now, callee function has no idea if the result will be used or not (unless it is inlined). So it always has to 'return' a value - put int into RAX or initialize object in provided space. And even if the value is not used, at a caller site code still needs to allocate the space - as it knows called function will be putting data into it. Since calling code knows the space is not going to be used, it will be discarded and no copies will be made at the calling site. There still will be a copy in the callee!

Now, even more interesting. Enter compiler optimizations. Depending on the size of your caluclate_matrix function, compiler may decide to inline it. If this is to happen, all 'argument passing' will simply go away - there will be nothing to be passed, as the code will be simply executed in the call site as if no function was called at all. When this happens, there would be no copy ever, and the whole return statement would likely be optimized away - there would be nowhere to return it.

I hope, this does answer the question.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
0

Regarding your simple example, I compiled it with g++ -o void void.cc on x86_64 Linux, and disassembled it with gdb. Here is what I got:

(gdb) disass main
Dump of assembler code for function main:
   0x00000000004004f8 <+0>: push   %rbp
   0x00000000004004f9 <+1>: mov    %rsp,%rbp
   0x00000000004004fc <+4>: callq  0x4004ed <_Z3foov>
   0x0000000000400501 <+9>: mov    $0x0,%eax
   0x0000000000400506 <+14>:    pop    %rbp
   0x0000000000400507 <+15>:    retq   
End of assembler dump.
(gdb) disass foo
Dump of assembler code for function _Z3foov:
   0x00000000004004ed <+0>: push   %rbp
   0x00000000004004ee <+1>: mov    %rsp,%rbp
   0x00000000004004f1 <+4>: mov    $0x4,%eax
   0x00000000004004f6 <+9>: pop    %rbp
   0x00000000004004f7 <+10>:    retq   
End of assembler dump.

As you see, the value 4 is being returned from foo() via the eax register, but it is being ignored in main().

Regarding your Matrix code. Returning a Matrix by value is expensive, as a copy would have to be made. As BeyelerStudios suggested in the comment, you can avoid a copy by returning const Matrix &. Another option is to make _calculate_matrix() take Matrix& as argument expecting an object in some initial usable state and fill it as the matrix is being computed.

Sasha Pachev
  • 5,162
  • 3
  • 20
  • 20
  • Returning by value is probably not that expensive with modern compiler optimisation and move semantics. I'm assuming it's some kind of 3x3 or 4x4 transformation matrix here of course. – Robinson Nov 20 '15 at 19:11
  • Returning a matrix may be expensive. But given that most compiler have very good RVO and NRVO optimizations built in, in reality they don't because the result will be built in place at the destination and the copy elided. – Martin York Nov 20 '15 at 19:25
  • @Robinson, there is NRVO or RVO in place here, and can not be. Read my answer. – SergeyA Nov 20 '15 at 19:30