为什么释放无效指针在 C++ 中未定义?
Why is freeing invalid pointers left undefined in C++?
考虑以下程序:
#include <iostream>
int main()
{
int b=3;
int* a=&b;
std::cout<<*a<<'\n';
delete a; // oops disaster at runtime undefined behavior
}
好的,根据 C++ 标准,程序的行为是未定义的。但我的问题是为什么它没有定义?为什么 C++ 的实现不给出任何编译器错误或任何警告?是否真的很难确定指针的有效性(意味着在编译时检查指针是否由 new 返回?)静态确定指针的有效性(即编译时)是否涉及任何开销?
不可能在编译时确定指针指向什么,这里有一个例子来说明这一点:
volatile bool newAlloc;
int main()
{
int b=3;
int* a;
if(newAlloc)
{
a = new int;
} else {
a = &b;
}
std::cout<<*a<<'\n';
delete a; // impossible to know what a will be
}
在一般情况下不可能在编译时确定指针是否有效。例如,如果你有一个函数作为库的一部分,它接受一个指针作为参数,编译器不能确定它总是有一个有效的指针传递给它。
标准将删除无效指针保留为未定义行为,否则每次删除或取消引用指针时都必须在运行时进行检查,这会导致语言设计者没有的性能损失想要。
Why implementations of C++ don't give any compiler errors or any warnings?
Clang 静态分析器做到了。
对于您的代码片段,您可以获得:
$ scan-build clang++ main.cpp
scan-build: Using '/usr/bin/clang' for static analysis
main.cpp:7:5: warning: Argument to 'delete' is the address of the local variable 'b', which is not memory allocated by 'new'
delete a; // oops disaster at runtime undefined behavior
^~~~~~~~
1 warning generated.
scan-build: 1 bug found.
如前所述,并非总是可以在编译时确定指针是否有效,但静态分析器绝对可以帮助您。
这里是实现细节答案:
new
/delete
实现必须有一些方法来跟踪关于通过 new
分配的所有块的一些数据,尤其是关于它们的大小。通常,这是通过将此数据存储在分配块开始 之前的字节 中来完成的。这样,delete
的实现可以非常快速地确定如何处理该块。当然,如果您传入无效地址,delete
实现只会在那里找到虚假数据,可能会导致您的应用程序崩溃或默默地破坏数据。但这没关系,因为传入无效地址是未定义的行为。
现在,让传递无效指针合法化会产生什么后果?
new
/delete
实现不能只从传入的指针中查找此信息,因为那可能是虚假信息。检索必要信息的唯一方法是 在 table 当前有效指针 中查找给定指针。这样的查找是相当昂贵的,即使使用快速查找算法也是如此; 很多比取消引用给定的指针慢很多,无论如何。
所以这又是一个 speed/safety 权衡,C++ 在这个例子中选择了速度。
考虑以下程序:
#include <iostream>
int main()
{
int b=3;
int* a=&b;
std::cout<<*a<<'\n';
delete a; // oops disaster at runtime undefined behavior
}
好的,根据 C++ 标准,程序的行为是未定义的。但我的问题是为什么它没有定义?为什么 C++ 的实现不给出任何编译器错误或任何警告?是否真的很难确定指针的有效性(意味着在编译时检查指针是否由 new 返回?)静态确定指针的有效性(即编译时)是否涉及任何开销?
不可能在编译时确定指针指向什么,这里有一个例子来说明这一点:
volatile bool newAlloc;
int main()
{
int b=3;
int* a;
if(newAlloc)
{
a = new int;
} else {
a = &b;
}
std::cout<<*a<<'\n';
delete a; // impossible to know what a will be
}
在一般情况下不可能在编译时确定指针是否有效。例如,如果你有一个函数作为库的一部分,它接受一个指针作为参数,编译器不能确定它总是有一个有效的指针传递给它。
标准将删除无效指针保留为未定义行为,否则每次删除或取消引用指针时都必须在运行时进行检查,这会导致语言设计者没有的性能损失想要。
Why implementations of C++ don't give any compiler errors or any warnings?
Clang 静态分析器做到了。
对于您的代码片段,您可以获得:
$ scan-build clang++ main.cpp
scan-build: Using '/usr/bin/clang' for static analysis
main.cpp:7:5: warning: Argument to 'delete' is the address of the local variable 'b', which is not memory allocated by 'new'
delete a; // oops disaster at runtime undefined behavior
^~~~~~~~
1 warning generated.
scan-build: 1 bug found.
如前所述,并非总是可以在编译时确定指针是否有效,但静态分析器绝对可以帮助您。
这里是实现细节答案:
new
/delete
实现必须有一些方法来跟踪关于通过 new
分配的所有块的一些数据,尤其是关于它们的大小。通常,这是通过将此数据存储在分配块开始 之前的字节 中来完成的。这样,delete
的实现可以非常快速地确定如何处理该块。当然,如果您传入无效地址,delete
实现只会在那里找到虚假数据,可能会导致您的应用程序崩溃或默默地破坏数据。但这没关系,因为传入无效地址是未定义的行为。
现在,让传递无效指针合法化会产生什么后果?
new
/delete
实现不能只从传入的指针中查找此信息,因为那可能是虚假信息。检索必要信息的唯一方法是 在 table 当前有效指针 中查找给定指针。这样的查找是相当昂贵的,即使使用快速查找算法也是如此; 很多比取消引用给定的指针慢很多,无论如何。
所以这又是一个 speed/safety 权衡,C++ 在这个例子中选择了速度。