Layout of std::shared_ptr (libstdc++)

Source code

#include <memory>
  
int main()
{
  std::shared_ptr<int> sPtr1 = std::make_shared<int>(42);
  std::shared_ptr<int> sPtr2 = sPtr1;
  return 0;
}
$ g++ --version
g++ (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.0

$ g++ main.cpp -std=c++11 -g

Debugging

$ gdb a.out
(gdb) b 7
(gdb) r
Breakpoint 1, main () at main.cpp:7
7	  return 0;

// Shared pointer with Pretty printer
(gdb) enable pretty-printer
163 printers enabled
163 of 163 printers enabled
(gdb) p sPtr1
$1 = std::shared_ptr<int> (use count 2, weak count 0) = {get() = 0x615c30}
(gdb) p sPtr2
$2 = std::shared_ptr<int> (use count 2, weak count 0) = {get() = 0x615c30}

// Shared pointer without Pretty printer
(gdb) disable pretty-printer
163 printers disabled
0 of 163 printers enabled
(gdb) p sPtr1
$3 = {<std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>> = {<std::__shared_ptr_access<int, (__gnu_cxx::_Lock_policy)2, false, false>> = {<No data fields>}, _M_ptr = 0x615c30, _M_refcount = {_M_pi = 0x615c20}}, <No data fields>}
(gdb) p sPtr2
$4 = {<std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>> = {<std::__shared_ptr_access<int, (__gnu_cxx::_Lock_policy)2, false, false>> = {<No data fields>}, _M_ptr = 0x615c30, _M_refcount = {_M_pi = 0x615c20}}, <No data fields>}

(gdb) p &sPtr1
$5 = (std::shared_ptr<int> *) 0x7fffffffdca0
(gdb) p sizeof(sPtr1)
$6 = 16
(gdb) p &sPtr2
$7 = (std::shared_ptr<int> *) 0x7fffffffdcb0
(gdb) p sizeof(sPtr2)
$8 = 16

// Object (The same behind sPtr1 and sPtr2)
(gdb) p *sPtr1._M_ptr
$9 = 42
(gdb) p sizeof(*sPtr1._M_ptr)
$10 = 4

// Control block (The same behind sPtr1 and sPtr2)
(gdb) p *sPtr1._M_refcount._M_pi
$11 = {<std::_Mutex_base<(__gnu_cxx::_Lock_policy)2>> = {<No data fields>}, _vptr._Sp_counted_base = 0x401970 <vtable for std::_Sp_counted_ptr_inplace<int, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>+16>, _M_use_count = 2, _M_weak_count = 1}
(gdb) p sizeof(*sPtr1._M_refcount._M_pi)
$12 = 16

// Both shared_ptr (Stack)
(gdb) x/4x 0x7fffffffdca0
0x7fffffffdca0:	0x00615c30	0x00000000	0x00615c20	0x00000000
(gdb) x/4x 0x7fffffffdcb0
0x7fffffffdcb0:	0x00615c30	0x00000000	0x00615c20	0x00000000

// Object (Heap)
(gdb) x/d 0x00615c30
0x615c30:	42

// Control block (Heap)
(gdb) x/4x 0x00615c20
0x615c20:	0x00401970	0x00000000	0x00000002	0x00000001
(gdb) info symbol 0x401970
vtable for std::_Sp_counted_ptr_inplace<int, std::allocator<int>, (__gnu_cxx::_Lock_policy)2> + 16 in section .rodata of /<path_to_binary>/a.out

Visual result

|----------------------|
|   shared_ptr sPtr1   |
|----------------------|
|Pointer to the Object |---|-------> Object
|                      |   |           (Int value 42)
|Pointer to the Ctr blk|---|---|
|----------------------|   |   |---> Ctl block
                           |   |       (Pointer to allocator, deleter, ...)
|----------------------|   |   |       (Shared reference counter)
|   shared_ptr sPtr2   |   |   |       (Weak reference counter + offset 1)
|----------------------|   |   |
|Pointer to the Object |---|   |
|                      |       |
|Pointer to the Ctr blk|-------|
|----------------------|
                         Stack (Frame 0)
                      ---------------------
      0x7fffffffdcb8: 0x00615c20 0x00000000 (Ctl block)
sPtr2 0x7fffffffdcb0: 0x00615c30 0x00000000 (Object)
      0x7fffffffdca8: 0x00615c20 0x00000000 (Ctl block)
sPtr1 0x7fffffffdca0: 0x00615c30 0x00000000 (Object)
                      ---------------------
                               ↓

                               ↑
                              Heap
                      ---------------------
Obj   0x000000615c30: 0x0000002a 0x00000000 (Int value 42)
      0x000000615c2c: 0x00000001            (Weak reference counter + offset 1)
      0x000000615c28: 0x00000002            (Shared reference counter)
C Blk 0x000000615c20: 0x00401970 0x00000000 (Pointer to allocator, deleter, ...)
                      ---------------------

Insides

If you create the shared pointer not with std::make_shared but with an explicit new the object and the control block are stored at two different memory locations. The control block contains then an additional pointer to the object.

When the shared/strong reference counter reaches zero the object gets destroyed. Afterwards only if the weak reference counter is/reaches zero the control block gets destroyed.