delete[] 运算符在非常简单的情况下导致分段错误

Posted

技术标签:

【中文标题】delete[] 运算符在非常简单的情况下导致分段错误【英文标题】:delete[] operator causes segmentation fault in very simple case 【发布时间】:2016-08-20 23:59:52 【问题描述】:

当我在分配的动态数组(使用 new 关键字创建)上调用 delete[] 时,会发生一个非常奇怪的分段错误。一开始是在我删除一个全局指针的时候发生的,但是在下面这个非常简单的情况下也发生了,我delete[] arr

int main(int argc, char * argv [])

     double * arr = new double [5];
     delete[] arr;

我收到以下消息:

*** Error in `./energy_out': free(): invalid next size (fast):  0x0000000001741470 ***
Aborted (core dumped)

除了main函数之外,我定义了一些相当标准的函数,以及以下(定义在main函数之前)

vector<double> cos_vector()

    vector<double> cos_vec_temp = vector<double>(int(2*pi()/trig_incr));
    double curr_val = 0;
    int curr_idx = 0;
    while (curr_val < 2*pi())
    
        cos_vec_temp[curr_idx] = cos(curr_val);
        curr_idx++;
        curr_val += trig_incr;
    

    return cos_vec_temp;


const vector<double> cos_vec = cos_vector();

请注意,cos_vector 的返回值 cos_vec_temp 在调用 main 函数之前被分配给全局变量 cos_vec

问题是,我知道导致错误的原因:cos_vec_temp 应该大一个元素,因为cos_vec_temp[curr_idx] 最终会访问超过向量 cos_vec_temp 末尾的一个元素。当我在创建时使cos_vec_temp 增大一个元素时,不会发生错误。但我不明白为什么会出现在arrdelete[]。当我运行 gdb 时,在 main 函数的开头设置断点后,在创建 arr 之后,检查变量内容时得到以下输出:

(gdb) p &cos_vec[6283]
$11 = (__gnu_cxx::__alloc_traits<std::allocator<double> >::value_type *)  0x610468

(gdb) p arr
$12 = (double *) 0x610470

在第一个 gdb 命令中,我显示了元素在cos_vec 向量末尾的内存位置,即0x610468。第二个 gdb 命令显示了arr 指针的内存位置,即0x610470。由于我将double 分配给了无效的内存位置0x610468,因此我知道它一定部分覆盖了从0x610470 开始的位置,但这是在arr 甚至创建之前完成的(该函数之前调用过main)。那么为什么这会影响arr?我原以为在创建arr 时,它不会“关心”之前对那里的内存位置所做的事情,因为它没有注册为正在使用中。

任何澄清将不胜感激。

注意:

cos_vec_temp 之前被声明为大小为int(2*pi()/trig_incr) 的动态双精度数组(与代码中的大小相同,但使用new 创建)。在那种情况下,我也有上述无效访问,当我访问该位置的元素时,它也没有给出任何错误。但是,当我尝试在 cos_vec 全局变量(当时的类型为 double *)上调用 delete[] 时,它也给出了分段错误,但它没有给出我在上述情况下得到的消息。

注意 2:

在你反对我使用动态数组之前,我只是好奇为什么会发生这种情况。我通常使用 STL 容器及其所有便利(我几乎从不使用动态数组)。

【问题讨论】:

您的cos_vector 超出了向量。请改用push_back 来避免手动计算大小。 你识别UB,UB就是UB... 解决此类问题的正确工具是使用调试器,但在这样做之前不要在 Stack Overflow 询问。告诉我们您在逐行检查代码时所做的所有观察。此外,您可能还想阅读 How to debug small programs (by Eric Lippert) 至少给我们留下一个 minimal reproducible example 来重现您的问题。 (这是πάνταῥεῖ™提供的个人股票评论) 堆损坏通常在损坏发生很久之后才显现出来。这可能会导致一些不相关的堆操作失败。 @KonradKapp 如果你使用at() 而不是[ ] 来访问你的向量,你的代码会抛出一个out_of_range 异常而不是让你在黑暗中因为分段错误或未定义的行为。 【参考方案1】:

许多堆分配器将元数据存储在它为您分配的内存旁边,在内存之前或之后(或两者)。如果您写出一些堆分配内存的边界(并记住std::vector 动态分配堆外),您可能会覆盖其中的一些元数据,破坏堆。

这些都没有在 C++ 规范中实际指定。它只是说越界会导致未定义的行为。分配器做什么或存储什么以及它可能存储元数据的位置取决于实现。


至于解决方案,大多数人告诉您使用push_back 而不是直接索引,解决问题。不幸的是,这也意味着向量需要重新分配和复制几次。这可以通过reserving 预先大约内存量来解决,然后让额外的杂散元素导致重新分配和复制。

或者,或者当然,更好地预测向量将包含的实际元素数量。

【讨论】:

+1:感谢您给出第一个体面的答案(即回答我提出的实际问题)。是的,我知道我应该push_back(我通常使用它,否则我会按照您的建议保留内存,但程度较小)。【参考方案2】:

看起来您正在写超出在 main 之前执行的函数中分配的向量的末尾,导致稍后出现未定义的行为。

您应该能够通过在分配向量时将数字向上舍入(转换为 int 将数字向下舍入)或使用 push_back 而不是索引来解决此问题:

cos_vec_temp.push_back(cos(curr_val));

【讨论】:

您没有阅读整个问题....叹息....别担心,从 cmets 判断,其他人也没有。很快就会关闭。 @KonradKapp 太糟糕了,因为使用 push_back 可能会解决它:-)

以上是关于delete[] 运算符在非常简单的情况下导致分段错误的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个非常简单的构造函数会导致段错误?

std::string 的声明导致 OpenGL 出现分段错误

如果子进程导致分段错误,multiprocessing.Pool 将挂起

简单的 PyQt5 QML 应用程序导致分段错误

dlclose() 后访问共享库分配的内存

C++学习32 重载new和delete运算符