0

Consider this snippet:

#include <memory>
#include <iostream>

struct A
{
    const int& getv()
    {
        return v;
    }
    
    int v{42};
};

struct Shared
{
    Shared(std::shared_ptr<A> someA)
    : intRef{someA->getv()}
    {
    }
    
    const int& intRef;
};

Shared createShared()
{
    auto sharedA = std::make_shared<A>();
    return Shared(sharedA);
}

int main()
{
    auto shared = createShared();
    std::cout <<"Value of intRef " << shared.intRef;
    
    return 0;
}

Why is there no segfault when accessing intRef in std::cout <<"Value of intRef " << shared.intRef?

Shared takes a shared_ptr<A> and stores a reference to the int in A.

The function createShared() creates a shared_ptr<A> and passes this to the constructor of Shared.

When createShared() returns, the shared_ptr<A> gets deleted.

I would have thought that any subsequent access to intRef of the object returned from createShared() will segfault, since the heap no longer holds this object.

Is my reasoning correct and is it just a coincidence that the segfault does not happen right on the spot?

In general, can you guide me to a coding guideline rule of CppCoreGuidelines where one should not take references of contents which live behind a smart pointer?

Juergen
  • 3,489
  • 6
  • 35
  • 59
  • 3
    Segfaults are a blessing from the gods. They tell you when something went wrong without a shadow of a doubt. But the gods are capricious. Sometimes they sit back and laugh at our mortal failings. Never count on a segfault. They are almost always the result of undefined behaviour, and the behaviour of undefined behaviour is undefined.. – user4581301 Nov 15 '20 at 03:45
  • 2
    Practically speaking, though, when a program releases memory the underlying system rarely reclaims it immediately. The program may ask for it again, the computer might not need it for anything else yet, so why waste the time reclaiming it. Many, many more reasons why you can still access the memory. You just can't do it safely. – user4581301 Nov 15 '20 at 03:49
  • Behind a few layers obscuring the picture, `createShared()` in effect returns a reference to a local variable. It's little different from `int& dontDoThis() { int v = 42; return v; }` – Igor Tandetnik Nov 15 '20 at 03:50
  • 1
    [Discussion of a similar topic](https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope). Where the memory comes from, automatic or dynamic storage, doesn't matter all that much when discussing what happens after it's been released.. – user4581301 Nov 15 '20 at 03:54
  • Is the topic related toR.37: Do not pass a pointer or reference obtained from an aliased smart pointer of the CppCoreGuidelines? Need to convince some colleagues... – Juergen Nov 15 '20 at 03:57
  • 1
    If you take a pointer of reference you need to make sure its lifetime is at least long as that of the referred to object. Otherwise, if you have a shared_ptr you can get a weak pointer. – Artefacto Nov 15 '20 at 03:59
  • By the way, you could confirm the code was incorrect by running in valgrind for instance, or using the memory sanitizer – Artefacto Nov 15 '20 at 04:00
  • R37 is certainly saying "Don't do that!" with respect to the above, but it's more concerned with passing as a parameter than returning. Returning a reference has many obvious failure cases. Calling a function with a reference requires more subtle shenanigans. Usually if you call a function the calling functions copy of the smart pointer will keep it live. But if the smart pointer is a member variable (or , god forbid, a global) that'll be replaced/reset/nuked further down the call stack or by another thread, kaboom. – user4581301 Nov 15 '20 at 04:29

0 Answers0