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
增大一个元素时,不会发生错误。但我不明白为什么会出现在arr
的delete[]
。当我运行 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 出现分段错误