Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RVO overwriting value in parameter before return

Compiling the following with xlC on AIX results in code that prints "2 2". On Linux with gcc and clang it reliably produces "3 3".

#include <iostream>

struct Numbers
{
    Numbers() : a(0) , b(0) { } 
    Numbers(int a, int b) : a(a), b(b) { }

    int a;
    int b;
};

Numbers combine(const Numbers& a, const Numbers& b)
{
    Numbers x;
    x.a = a.a + b.a;
    x.b = a.b + b.b;
    return x;
}

Numbers make()
{
    Numbers a(1, 1);
    Numbers b(2, 2);

    a = combine(a, b);
    return a;
}

int main()
{
    Numbers a = make();
    std::cerr << a.a << " " << a.b << "\n";
}

It looks to me like AIX is applying RVO to the return value of combine, so when I create Numbers x, it ends up overwriting my parameter a with the default initialised x.

Am I invoking some undefined behaviour here? I would expect that no modifications are made to a until after combine(a, b) has been evaluated and assigned to a.

This is with: IBM XL C/C++ for AIX, V12.1 (5765-J02, 5725-C72) Version: 12.01.0000.0012

like image 564
Hamish Morrison Avatar asked Aug 24 '17 16:08

Hamish Morrison


1 Answers

It looks like the compiler is performing copy elision on the copy assignment(!) where it could really only do so on an initialization. Which is to say that the compiler is indeed overwriting the object associated with your parameter a when initializing x. Having an application of RVO (for some definition of RVO) to the return value of combine is itself not wrong. What is wrong is the target of the RVO (which should be a temporary in the scope of make and not the object associated with a in make).

Adding a user provided copy assignment operator should work as a workaround:

Numbers &operator=(const Numbers &other) { a = other.a; b = other.b; return *this; }
like image 160
Hubert Tong Avatar answered Oct 19 '22 10:10

Hubert Tong