Home > database >  C Vector Returned from function not printing right values
C Vector Returned from function not printing right values

Time:11-17

I have class A and a main() function:

class A {
    public: int num;
    public: A* parent;

    A(){};

    A::A (const A &s)
    {
        this->num = s.num;
        this->parent = s.parent;
    }

    public : vector <A> foo(A a)
    {
        A a1;
        a1.num = a.num;
        a1.parent = &a;
    
        vector <A> list;
        list.reserve(1);
        list.push_back(a1);
        
        A temp = list.front();
    
        cout << temp.parent->num << endl;

        return list;
    }
}; 

int main()
{
    A a; 
    a.num =2;

    vector <A> list = a.foo(a);

    A temp = list.front();

    cout << temp.parent->num << endl;
    return 0;
}

The problem is that cout inside foo() prints 2 as expected, but cout inside main() is printing a huge number.

I assume the problem is something related to memory rellocation, as I read from multiple sources, so I tried using reserve() but it didn't work.

Can anyone help me solve this problem?

CodePudding user response:

In foo(), you are passing in the a parameter by value. That means the A object that main() is passing to foo() gets copied into the a parameter. The a parameter itself is a local variable to foo(). As such, a1.parent is being set to point at a local variable.

And then list.push_back(a1); makes a copy of a1, which copies the parent pointer. And then you are making another copy when saving the result of list.front() to temp.

Consequently, dereferencing the temp.parent pointer inside of foo() is perfectly valid, since a that parent is pointing at is still valid in memory.

However, once foo() exits, a gets destroyed, leaving the copied parent pointer(s) dangling, pointing at now-invalid memory. As such, in main(), dereferencing the parent pointer is undefined behavior since that pointer is no longer pointing at the original a object.

You can easily see this for yourself if you add some logging for the construction and destruction of your A objects, eg:

class A {
public:
    int num = 0;
    A* parent = nullptr;
 
    A(){ cout << "A created: " << this << endl; }
 
    A(const A &s) : num(s.num), parent(s.parent)
    {
        cout << "A copied: " << &s << " -> " << this << endl;
    }
 
    ~A(){ cout << "A destroyed: " << this << endl; }

    ...
};

Online Demo

You will see something like this being logged:

A created: 0x7ffe5e735250                   // 'a' in main() is created
A copied: 0x7ffe5e735250 -> 0x7ffe5e735260  // 'a' in foo() is passed in by main()
A created: 0x7ffe5e7351e0                   // 'a1' in foo() is created
A copied: 0x7ffe5e7351e0 -> 0x5592f50b2e80  // 'a1' is copied into `list` in foo()
A copied: 0x5592f50b2e80 -> 0x7ffe5e7351f0  // list.front() is copied into 'temp' in foo()
(foo) temp.parent: 0x7ffe5e735260, num: 2   // parent is pointing at 'a' in foo()
                                            // foo() exits, 'list' is transferred to main()
A destroyed: 0x7ffe5e7351f0                 // 'temp' in foo() is destroyed
A destroyed: 0x7ffe5e7351e0                 // 'a1' in foo() is destroyed
A destroyed: 0x7ffe5e735260                 // 'a' in foo() is destroyed !!!!!
A copied: 0x5592f50b2e80 -> 0x7ffe5e735260  // main() reuses freed memory for 'temp' in main() !!!!!
(main) temp.parent: 0x7ffe5e735260, num: 2  // parent is pointing at the wrong A object !!!!!
                                            // main exits
A destroyed: 0x7ffe5e735260                 // 'temp' in main() is destroyed
A destroyed: 0x5592f50b2e80                 // `list` in main() is destroyed
A destroyed: 0x7ffe5e735250                 // 'a' in main() is destroyed

To solve this issue, change foo() to take in the A object by reference instead (and also change the two temp variables to be A& references, as well):

vector<A> foo(A &a)

This way, in main(), the returned parent pointer will remain valid as long as the local a object that is being passed into foo() remains valid.

Online Demo

Now, you will see something like this being logged instead:

A created: 0x7ffd7319e8b0                   // 'a' in main() is created
                                            // 'a' in foo() is passed in by main()
A created: 0x7ffd7319e850                   // 'a1' in `foo() is created
A copied: 0x7ffd7319e850 -> 0x556ccff0fe80  // 'a1' is copied into 'list' in foo()
(foo) temp.parent: 0x7ffd7319e8b0, num: 2   // parent is pointing at 'a' in main()
                                            // foo() exits, 'list' is transferred to main()
A destroyed: 0x7ffd7319e850                 // 'a1' in foo() is destroyed
(main) temp.parent: 0x7ffd7319e8b0, num: 2  // parent is still pointing at 'a' in main()
                                            // main() exits
A destroyed: 0x556ccff0fe80                 // 'list' in main() is destroyed. 
A destroyed: 0x7ffd7319e8b0                 // 'a' in main() is destroyed !!!!!
  • Related