0

Please find the code attached which I took from https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/ for testing the smart pointers.

// C++ program to demonstrate shared_ptr
#include <iostream>
#include <memory>

class A {
public:
    void show()
    {
        std::cout << "A::show()" << std::endl;
    }
};

int main()
{
    std::shared_ptr<A> p1(new A);
    std::cout << p1.get() << std::endl;
    p1->show();
    std::shared_ptr<A> p2(p1);
    p2->show();
    std::cout << p1.get() << std::endl;
    std::cout << p2.get() << std::endl;

    // Returns the number of shared_ptr objects
    // referring to the same managed object.
    std::cout << p1.use_count() << std::endl;
    std::cout << p2.use_count() << std::endl;

    // Relinquishes ownership of p1 on the object
    // and pointer becomes NULL
    p1.reset();
    std::cout << p1.get() << std::endl;
    std::cout << p2.use_count() << std::endl;
    std::cout << p2.get() << std::endl;
    p1->show();
    p2->show();
    std::cout << p1.get() << std::endl;
    std::cout << p2.use_count() << std::endl;
    std::cout << p2.get() << std::endl;

    return 0;
}

p1 is reset and no pointer is assigned to it (Found as per p1.get()). After that, When I am calling p1->show() function, it shows the output as A::show(). How it is possible? is it a same case on raw pointers as well?

output:
0x24dc5ef1790
A::show()
A::show()
0x24dc5ef1790
0x24dc5ef1790
2
2
0
1
0x24dc5ef1790
A::show() 
A::show()
0
1
0x24dc5ef1790
subha
  • 123
  • 7
  • 1
    Try compiling with `-g -fsanitize=address,undefined` and then run your program. It's a very good combination of options to catch mistakes, like the one you've done in your code. It often manages to point at the exact violation/mistake done, like here: `p1->show()` - `member call on null pointer` - which means your program has undefined behavior. – Ted Lyngmo Aug 13 '22 at 01:27

2 Answers2

6

It works because you are not using any member from the object otherwise it would segfault. But a sanitizer catches this very easily:

$ g++ -ggdb -O0 -fsanitize=undefined,address shared.cpp -o shared
$ ./shared
0x602000000010
A::show()
A::show()
0x602000000010
0x602000000010
2
2
0
1
0x602000000010
shared.cpp:33:13: runtime error: member call on null pointer of type 'struct element_type'
A::show()
A::show()
0
1
0x602000000010

Change the code to add one member variable to print like

class A {
public:
    void show()
    {
        std::cout << "A::show() " << value << std::endl;
    }
    int value;
};

And it segfaults

$ g++ -ggdb -O0 shared.cpp -o shared
$ ./shared
0x560a3dde4eb0
A::show() 0
A::show() 0
0x560a3dde4eb0
0x560a3dde4eb0
2
2
0
1
0x560a3dde4eb0
Segmentation fault (core dumped)
Something Something
  • 3,999
  • 1
  • 6
  • 21
0

In your example, the show() function doesn't use any member variables, therefore it doesn't need this pointer. If we bear in mind that non-static member functions implicitly called with an additional this pointer, using non-static member variables in show() will require a this pointer. So it is obvious that if you use member variables in show, your example can crash, because the this pointer passed in will be nullptr in the case of calling resetted pointer.

Non-static member functions are called like this:

A::show(A* this)

Say that A has a member variable called id, and we print it in show()

A a;
a.show();

This converts to:

A::show(A*this)
{
  std::cout << "Id: " << this->id << " A::show()" << std::endl;
}

It enables an oppurtunity like calling the show() with nullptr:

std::invoke(&A::show, (A*)nullptr);

Unless you use nulled this pointer here, probably this won't crash.

But you shouldn't rely on that. It is undefined behaviour. Never dereference a nullptr, just like @Ted Lyngmo said.

CoderCat
  • 101
  • 1
  • 5
  • 1
    _"So it is obvious that if you use member variables in show, your example will crash"_ - No it's not. Read the other answers. – Ted Lyngmo Aug 13 '22 at 02:06
  • 1
    You're welcome. In this case, the problem arises a bit earlier. `p1`, which is a `nullptr`, is dereferenced, so whatever the member variables there are will not matter. Dereferencing `nullptr` is enough. – Ted Lyngmo Aug 13 '22 at 02:16
  • I don't think we are dereferencing a `nullptr` here. Because we really do not try to read the memory held by `p1`. We do not use pointer artichmetics on `nullptr`, we do not access it. We just implicitly post the value `0` as an argument to a function that compiler already knows. So what do you think that can cause undefined behaviour? – CoderCat Aug 13 '22 at 02:46
  • auto ptr = (A*)nullptr; ptr->show(); Is this undefined behaviour if we do not ever use `this` pointer in show? – CoderCat Aug 13 '22 at 02:48
  • `p1.reset();` ...`p1->show();` - This is pretty much the definition of dereferencing a `nullptr`. It'd be clearer with `something *ptr = nullptr;` and `ptr->show();` but the effect is the same. – Ted Lyngmo Aug 13 '22 at 02:48
  • Isn't `->` just a shorthand for `std::invoke(&A::show, (A*)nullptr);` – CoderCat Aug 13 '22 at 02:52
  • 1
    No, `operator->` is, for a class such as a smart pointer or an iterator class, often just letting the held pointer out. Not that it'd matter much. Invoking `show` via `std::invoke` isn't more than a smoke screen when the instance is `nullptr`. – Ted Lyngmo Aug 13 '22 at 02:55
  • 1
    I used the word 'shorthand' wrongly there. I actually meant something like 'it does the exact same thing'. But okay, i will think on that. Thank you for discussion. – CoderCat Aug 13 '22 at 03:01
  • 1
    You too! It's dereferencing the `nullptr` that matters. If you do it via a proxy or not doesn't matter. – Ted Lyngmo Aug 13 '22 at 03:03