为什么gmp会在这里与"invalid next size"重新定位一起崩溃?

Why does gmp crash with "invalid next size" to realloc here?

本文关键字:定位 崩溃 一起 新定位 size 在这里 gmp invalid 为什么 next      更新时间:2023-10-16

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

#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 的无效读取,但列出了与它后面的其余跟踪对应的堆栈条目,然后说在下一条指令中有一个无效的写入。 无效读取是"大小为 24 的块 [由make_unique] 分配"之后的 8 个字节,而无效写入为 null。 显然,这一行也不应该引起,因为它应该只读取一个指针,然后写入它指向的缓冲区的一部分,而缓冲区绝对没有地址0x0。 我决定使用 C++ 绑定,即使我总是使用 C 的 gmp,因为我认为编写会更快,但此错误确保情况并非如此。 这是 gmp 的问题还是我分配了错误的阵列? 如果我直接使用newdelete,或者手动内联函数调用,我会收到类似的错误。 我觉得问题可能与实际存储表达式模板mpz_class而不是适当的具体化值有关。

我正在使用带有g++ -std=c++17 -O2 -g -Wall ...和 GMP 6.1.2-3 的 GCC 9.2.0。 Clang和GCC都没有报告任何错误。

如果我们在瓦尔格林德下运行,我们会看到:

==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等。