The way you mismatched malloc/new is invoking UB (as ubsan+asan will readily tell you).
==32752==ERROR: AddressSanitizer: alloc-dealloc-mismatch (malloc vs operator delete
#0 0x7fb58c15c407 in operator delete(void*, unsigned long) (/usr/lib/x86_64-lin
#1 0x564b19759014 in __gnu_cxx::new_allocator<char>::deallocate(char*, unsigned
#2 0x564b1974b8cb in std::allocator<char>::deallocate(char*, unsigned long) /us
#3 0x564b1974b8cb in std::allocator_traits<std::allocator<char> >::deallocate(s
#4 0x564b197478f4 in std::__cxx11::basic_string<char, std::char_traits<char>, s
#5 0x564b19744f74 in std::__cxx11::basic_string<char, std::char_traits<char>, s
#6 0x564b19741053 in std::__cxx11::basic_string<char, std::char_traits<char>, s
#7 0x564b19744993 in std::ostream& boost::multiprecision::operator<< <boost::mu
#8 0x564b1973da5a in main /home/sehe/Projects/stackoverflow/test.cpp:14
#9 0x7fb58ab85bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6
#10 0x564b1973d669 in _start (/home/sehe/Projects/stackoverflow/sotest+0x4a669)
So let's focus on the claim:
#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>
using Int = boost::multiprecision::cpp_int;
int main() {
Int u("987654321");
std::cout << u << "\n";
}
When we ask clang to follow the overload for operator<<
it leads us here:
template <class Backend, expression_template_option ExpressionTemplates>
inline std::ostream& operator<<(std::ostream& os, const number<Backend, ExpressionTemplates>& r)
{
std::streamsize d = os.precision();
std::string s = r.str(d, os.flags());
std::streamsize ss = os.width();
if (ss > static_cast<std::streamsize>(s.size()))
{
char fill = os.fill();
if ((os.flags() & std::ios_base::left) == std::ios_base::left)
s.append(static_cast<std::string::size_type>(ss - s.size()), fill);
else
s.insert(static_cast<std::string::size_type>(0), static_cast<std::string::size_type>(ss - s.size()), fill);
}
return os << s;
}
As you can see the number is taken by const-reference, so no copy is performed. There will be allocations for the buffers (in the str()
implementation). I don't think the Multiprecision library is going to boast a highly optimized implementation of IO operations.
Memory Profiling
To see exactly what allocations are done where, I ran a debug build through Massif:

At the peak, the top allocations are:
99.97% (73,759B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->98.54% (72,704B) 0x50DDFA4: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
| ->98.54% (72,704B) 0x40108F1: _dl_init (dl-init.c:72)
| ->98.54% (72,704B) 0x40010C8: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so)
|
->01.39% (1,024B) 0x58C526A: _IO_file_doallocate (filedoalloc.c:101)
| ->01.39% (1,024B) 0x58D5447: _IO_doallocbuf (genops.c:365)
| ->01.39% (1,024B) 0x58D4566: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:759)
| ->01.39% (1,024B) 0x58D2ABB: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1266)
| ->01.39% (1,024B) 0x58C6A55: fwrite (iofwrite.c:39)
| ->01.39% (1,024B) 0x516675A: std::basic_ostream<char, std::char_traits<ch
| ->01.39% (1,024B) 0x10C85C: std::ostream& boost::multiprecision::operat
| ->01.39% (1,024B) 0x10B6C6: main (test.cpp:8)
|
->00.04% (31B) in 1 place, below massif's threshold (1.00%)
Sadly somehow I cannot make the threshold < 1% (this might be a documented limit).
What we can see is that though the 31B allocation happens somewhere, it is dwarfed by the file output buffer (1024B).
If we replace the output statement by just
return u.str().length();
you can still witness the 31B allocation, which does NOT match the size of the cpp_int type. Indeed, iff we were to copy THAT:
return std::make_unique<Int>(u)? 0 : 1;
THEN we see a 32B allocation instead:
->00.04% (32B) in 1 place, below massif's threshold (1.00%)
It is pretty clear that the cpp_int is not being copied, as makes sense.