c++ - 为什么 gmp 会因 "invalid next size"崩溃而在此处重新分配?

标签 c++ unique-ptr gmp

我有一个使用 gmp C++ 绑定(bind)的简单函数:

#include <inttypes.h>
#include <memory>
#include <gmpxx.h>



mpz_class f(uint64_t n){
    std::unique_ptr<mpz_class[]> m = std::make_unique<mpz_class[]>(n + 1);
    m[0] = 0;
    m[1] = 1;
    for(uint64_t i = 2; i <= n; ++i){
        m[i] = m[i-1] + m[i-2];
    }
    return m[n];
}

int main(){
    mpz_class fn;
    for(uint64_t n = 0;; n += 1){
        fn = f(n);
    }
}

据推测,make_unique 应该分配一个新数组并在函数返回时释放它,因为拥有它的唯一指针的生命周期结束了。据推测,返回的 mpz_class 对象应该是一个拷贝,并且不受此数组删除的影响。程序因错误而崩溃:

realloc(): invalid next size

如果我查看 gdb 中的核心转储,我会得到堆栈跟踪:

#0 raise()
#1 abort()
#2 __libc_message()
#3 malloc_printerr()
#4 _int_realloc()
#5 realloc()
#6 __gmp_default_reallocate()
#7 __gmpz_realloc()
#8 __gmpz_add()
#9 __gmp_binary_plus::eval(v, w, z)
#10 __gmp_expr<...>::eval(this, this, p)
#11 __gmp_set_expr<...>(expr, z)
#12 __gmp_expr<...>::operator=<...>(expr, this)
#13 f(n)
#14 main(argc, argv)

这对我没有帮助,除了它表明问题可能来自使用表达式模板的 gmpxx(堆栈帧 9-12 表明这一点,valgrind 和堆栈帧 12 将我的代码的最后一行放在错误之前执行在 m[1] = 1;)。 Valgrind 说在这一行有一个大小为 8 的无效读取,但在它之后列出了对应于跟踪的其余部分的堆栈条目,然后说在下一条指令中有一个无效的写入。无效读取是“[由 make_unique] 分配的大小为 24 的 block ”之后的 8 个字节,而无效写入是 null。显然,这一行不应该导致任何一个,因为它应该只读取一个指针,然后写入它指向的缓冲区的一部分,该缓冲区肯定没有地址 0x0。我决定使用 C++ 绑定(bind),尽管我总是使用 C 中的 gmp,因为我认为编写起来会更快,但这个错误确保情况并非如此。这是 gmp 的问题还是我分配的数组有误?如果我直接使用 newdelete 或者如果我手动内联函数调用,我会得到类似的错误。我觉得问题可能与 mpz_class 实际上存储的是表达式模板而不是适当的具体化值有关。

我将 GCC 9.2.0 与 g++ -std=c++17 -O2 -g -Wall ... 和 GMP 6.1.2-3 一起使用。 Clang 和 GCC 均未报告任何错误。

最佳答案

如果我们在 Valgrind 下运行,我们会看到:

==1948514== Invalid read of size 8
==1948514==    at 0x489B0F0: __gmpz_set_si (in /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2)
==1948514==    by 0x10945E: __gmp_expr<__mpz_struct [1], __mpz_struct [1]>::assign_si(long) (gmpxx.h:1453)
==1948514==    by 0x1094E3: __gmp_expr<__mpz_struct [1], __mpz_struct [1]>::operator=(int) (gmpxx.h:1538)
==1948514==    by 0x109248: f(unsigned long) (59678712.cpp:8)
==1948514==    by 0x109351: main (59678712.cpp:18)
==1948514==  Address 0x4e08ca0 is 8 bytes after a block of size 24 alloc'd
==1948514==    at 0x483650F: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1948514==    by 0x10953F: std::_MakeUniq<__gmp_expr<__mpz_struct [1], __mpz_struct [1]> []>::__array std::make_unique<__gmp_expr<__mpz_struct [1], __mpz_struct [1]> []>(unsigned long) (unique_ptr.h:855)
==1948514==    by 0x10920C: f(unsigned long) (59678712.cpp:6)
==1948514==    by 0x109351: main (59678712.cpp:18)

这表明当我们调用 f(0) 时, 我们写信给 m[1] , 这是越界的。这是未定义的行为,所以任何事情都可能发生。幸运的是你遇到了崩溃,而不是更微妙的事情。

简单修复:

mpz_class f(uint64_t n) {
    if (!n) return 0;

顺便说一句,更喜欢<cstdint><inttypes.h> , 写成 std::uint64_t

关于c++ - 为什么 gmp 会因 "invalid next size"崩溃而在此处重新分配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59678712/

相关文章:

c - 问:从“struct __mpz_struct *”类型分配为“mpz_t”类型时是否存在不兼容的类型?

c - GMP 无法处理前导 "+"吗?

c++ - 如何确定文件夹是否存在以及如何创建文件夹?

c++ - Boost::Python- 可以从 dict 自动转换 --> std::map?

c++ - 为什么可以从另一个 unique_ptr get() 创建 unique_ptr 而不会导致错误?

C++ 交换 unique_ptr's

GMP 变量的位大小

c++ - log4cpp(Linux): can't write message into logfile

c++ - Cython、C++ 和 gsl

c++ - unique_ptr 的这两种用法有什么区别?