为什么 gmp 会因 "invalid next size" 崩溃而在此处重新分配?
Why does gmp crash with "invalid next size" to realloc here?
我有一个使用 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
应该分配一个新数组并在函数 returns 时释放它,因为拥有它的唯一指针已经结束了它的生命周期。据推测,返回的 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 的无效读取,但在它之后列出了对应于跟踪的其余部分的堆栈条目,然后说在下一条指令中有一个无效的写入。无效读取是 "a block of size 24 alloc'd [by make_unique
]" 之后的 8 个字节,而无效写入是 null。显然,这一行不应该导致任何一个,因为它应该只读取一个指针,然后写入它指向的缓冲区的一部分,该缓冲区肯定没有地址 0x0。我决定使用 C++ 绑定,尽管我总是使用 C 中的 gmp,因为我认为编写起来会更快,但这个错误确保情况并非如此。这是 gmp 的问题还是我分配的数组有误?如果我直接使用 new
和 delete
或者如果我手动内联函数调用,我会得到类似的错误。我觉得这个问题可能与 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
等等
我有一个使用 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
应该分配一个新数组并在函数 returns 时释放它,因为拥有它的唯一指针已经结束了它的生命周期。据推测,返回的 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 的无效读取,但在它之后列出了对应于跟踪的其余部分的堆栈条目,然后说在下一条指令中有一个无效的写入。无效读取是 "a block of size 24 alloc'd [by make_unique
]" 之后的 8 个字节,而无效写入是 null。显然,这一行不应该导致任何一个,因为它应该只读取一个指针,然后写入它指向的缓冲区的一部分,该缓冲区肯定没有地址 0x0。我决定使用 C++ 绑定,尽管我总是使用 C 中的 gmp,因为我认为编写起来会更快,但这个错误确保情况并非如此。这是 gmp 的问题还是我分配的数组有误?如果我直接使用 new
和 delete
或者如果我手动内联函数调用,我会得到类似的错误。我觉得这个问题可能与 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
等等