每次我尝试打印时,boost::multiprecision::cpp_int 都会被复制然后删除

boost::multiprecision::cpp_int is being copied then deleted every time I try and print it

当我从 Boost 打印 cpp_int 时,似乎复制了整个对象。

#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
using std::cout;

void* operator new(size_t size) {
    void *memory = malloc(size);
    cout << "New: " << memory << " " << size << "\n";
    return memory;
}

int main() {
    auto u = new boost::multiprecision::cpp_int("987654321");
    cout << "------\n";
    cout << *u << "\n";
}
New: 0x23d4e70 32
------
New: 0x23d52b0 31
987654321

令人困惑的是,用于打印的重载是 ostream& operator<<(ostream&, const T&),但是将 *u 传递给 template <typename T> void cr(const T&) {} 等函数不会显示任何新的内存分配。我也试过 u->str() 但这也会导致第二次内存分配。

我还尝试为 cpp_int:

重载 cout
std::ostream& operator <<(std::ostream& stream, const boost::multiprecision::cpp_int& mpi) {
    return stream << mpi.str();
}

但结果是一样的。但是,我也很惊讶这个编译,因为我预计已经过载了。我的假设是我可能需要修改更多后端内容。

我怎样才能避免这种情况?我不想每次打印 cpp_int.

时都复制然后删除 30 多个字节

如果不是,切换数据类型不是不可能的,只要接口相似以进行最小重构。

你不匹配的方式 malloc/new 是调用 UB(正如 ubsan+asan 会很容易告诉你的那样)。

==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/Whosebug/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/Whosebug/sotest+0x4a669)

所以让我们把重点放在声明上:

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

using Int = boost::multiprecision::cpp_int;

int main() {
    Int u("987654321");
    std::cout << u << "\n";
}

当我们要求 clang 跟随 operator<< 的重载时,它引导我们到这里:

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;
}

正如你所看到的,这个数字是由const-reference获取的,所以没有执行复制。将分配缓冲区(在 str() 实现中)。我认为 Multiprecision 库不会吹嘘高度优化的 IO 操作实现。

内存分析

为了准确了解在何处进行了哪些分配,我 运行 通过 Massif 进行了调试构建:

在高峰期,最高分配是:

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%)

遗憾的是我无法使阈值 < 1%(这可能是记录的限制)。

我们可以看到的是,虽然 31B 分配发生在某处,但它与文件输出缓冲区 (1024B) 相比相形见绌。

如果我们将输出语句替换为

return u.str().length();

您仍然可以看到 31B 分配,它与 cpp_int 类型的大小不匹配。事实上,如果我们要复制那个:

return std::make_unique<Int>(u)? 0 : 1;

然后我们看到的是 32B 分配:

->00.04% (32B) in 1 place, below massif's threshold (1.00%)

很明显 cpp_int 没有被复制,这是有道理的。