Hongxu Chen
Hongxu Chen

Reputation: 5360

segment fault for a simple std::shared_ptr construction case

From cppreference I learn that there is a constructor for std::shared_ptr:

template< class Y > explicit shared_ptr( Y* ptr );

And I tried a piece of code as follows:

#include <string>
#include <memory>
#include <iostream>

int main(void) {
  /// block 1
  {
    std::shared_ptr<std::string> s1(new std::string("good"));
    std::shared_ptr<std::string> s2(s1.get()); /// s2
    std::cerr << s2.use_count() << std::endl;
  }
  /// block 2
  {
    std::shared_ptr<int> i1(new int(1));
    std::shared_ptr<int> i2(i1.get());  ///  i2
    std::cerr << i2.use_count() << std::endl;
  }
  return 0;
}

It causes segment fault for block 1 but not for block 2, but both use_count are 1. The difference I can think about is that that int is a primitive type while std::string is managed by allocator.

I read bits/shared_ptr.h of gcc-4.9 and find that there is a post condition for this constructor:

use_count() == 1 && get() == __p

Question 1:

Should std::shared_ptr NOT constructed with a raw pointer that has been referenced by another smart pointer? In this sense, is the preferred way to use this constructor as follow?

std::shared_ptr<T>(new T(...));

Question 2:

Does the standard has explicit requirement for this constructor, or this post condition is ONLY for libstdc++?

Upvotes: 4

Views: 3212

Answers (2)

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158639

This is undefined behavior, we can see this by going to cppreference section for std::shared_ptr constructors says this (emphasis mine):

Constructing a shared_ptr using the raw pointer overload for an object that is already managed by a shared_ptr leads to undefined behavior, even if the object is of a type derived from std::enable_shared_from_this (in other words, raw pointer overloads assume ownership of the pointed-to object).

Both shared_ptr will attempt to delete the object they believe they now hold sole ownership for. We know this is undefined behavior by going to the draft C++ standard section 3.7.4.2 Deallocation functions which says:

If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. [...]

Ownership can only be shared by using copy construction or copy assignment. The same cppreference page provides a correct example using the copy constructor:

 std::cout << "constructor with object\n";
 std::shared_ptr<Foo> sh2(new Foo);
 std::shared_ptr<Foo> sh3(sh2);     // <- using copy construction

An alternative method to creating a shared_ptr is to use std::make_shared, which is safer:

Moreover, code such as f(std::shared_ptr(new int(42)), g()) can cause a memory leak if g throws an exception because g() may be called after new int(42) and before the constructor of shared_ptr. This doesn't occur in f(std::make_shared(42), g()), since two function calls are never interleaved.

Upvotes: 3

Both of these cases are invalid uses of std::shared_ptr.

You cannot pass the same raw pointer to two std::shared_ptr constructors and expect well-defined results. Both std::shared_ptrs will believe that they own that pointer, and will attempt to delete it when they go out of scope.

This is a double free, and is invalid.

If you want to have two std::shared_ptrs that manage the same object, you can construct one of them with a raw pointer (or, better yet, use std::make_shared), and then copy construct/assign the second one from the first one. This way the memory will only be deallocated (and the destructor for the object fired) when the last of those std::shared_ptrs goes out of scope.

The reason you're getting a segmentation fault in the first case, and not the second one, is likely because int is a trivial type, and therefore you're not looking through a freed pointer to run the destructor of int, because it doesn't have one.

Upvotes: 9

Related Questions