如何捕捉空指针异常? [复制]
Posted
技术标签:
【中文标题】如何捕捉空指针异常? [复制]【英文标题】:How to catch the null pointer exception? [duplicate] 【发布时间】:2010-12-21 21:03:05 【问题描述】: try
int* p = 0;
*p = 1;
catch (...)
cout << "null pointer." << endl;
我试图捕捉这样的异常,但它不起作用,有什么帮助吗?
【问题讨论】:
特别是对于 VC++,如果您使用cl.exe /EHa
编译,catch(...)
将捕获 AV。但是,如果你这样做了,愤怒的 C++ 之神会立即当场用闪电击倒你,所以忘记我告诉你的吧。
避免让我的程序崩溃,或者被闪电击中。一个古老的程序员的困境。
不要将 C++ 异常与微软命名不佳的系统“异常”混淆。
【参考方案1】:
c++ 中不存在 NULL 指针异常,但您仍想捕获相同的异常,然后您需要提供自己的类实现。
以下是相同的示例。
class Exception
public:
Exception(const string& msg,int val) : msg_(msg),e(val)
~Exception( )
string getMessage( ) const return(msg_);
int what() return e;
private:
string msg_;
int e;
;
现在基于 NULL 指针检查,它可以像 throw(Exception("NullPointerException",NULL));
一样抛出
以下是捕获相同的代码。
catch(Exception& e)
cout << "Not a valid object: " << e.getMessage( )<< ": ";
cout<<"value="<<e.what()<< endl;
【讨论】:
【参考方案2】:在 Visual Studio 中使用try
-> catch (...)
块有一种非常简单的方法可以捕获任何类型的异常(除以零、访问冲突等)。
一个小的项目调整就足够了。只需在项目设置中启用/EHa
选项即可。请参阅项目属性 -> C/C++ -> 代码生成 -> 将 Enable C++ Exceptions 修改为“Yes With SEH Exceptions”。就是这样!
在此处查看详细信息: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx
【讨论】:
【参考方案3】:通常你不能。即使你能做到,这就像试图在一艘出现泄漏的潜艇上贴上创可贴一样。
一个瘫痪的应用程序造成的损害比崩溃的应用程序要大得多。我的建议是让它崩溃,然后修复它崩溃的原因。冲洗。重复。
【讨论】:
也感谢上帝。这实际上有助于我认为更好的程序员和更好的代码。【参考方案4】:在 VC++ 2013(以及更早的版本)中,您可以在异常上设置断点:
-
按 Ctrl + Alt + Delete(这将打开异常对话框)。
展开“Win32 异常”
确保选中“0xC0000005 访问冲突”异常。
现在再次调试,将在 null 取消引用发生时准确地命中断点。
【讨论】:
【参考方案5】:简短的回答 - 你不能以可移植或标准的方式,因为这样的错误可能会破坏进程本身。
长答案 - 你可以做的比你想象的要多,而且绝对比程序崩溃的默认值要多。但是,您需要记住 3 件事: 1) 这些错误比异常更严重,并且通常不能作为逻辑异常出现。 2)您的检测和库处理将依赖于后端的平台,即使您可以提供一个干净的抽象接口供公众使用。 3) 总会有一些非常糟糕的崩溃,你甚至无法在结束之前发现它们。
基本上,像段错误或堆损坏这样的错误不是例外,因为它们正在破坏运行程序的实际进程。您编写到程序中的任何内容都是程序的一部分,包括异常处理,因此在进程终止之前记录一个很好的错误消息之外的任何事情在少数情况下都是不可取的,这并非不可能。在 POSIX 中,操作系统使用信号系统来报告此类故障,您可以注册回调函数以在退出之前记录错误内容。在 Windows 中,操作系统有时可以将它们转换为外观正常的异常,您可以从中捕获和恢复。
但是,最终,您最好的选择是针对此类噩梦进行防御性编码。在任何给定的操作系统上,都会有一些非常糟糕,以至于您无法检测到它们,即使在原则上,在您的进程死亡之前也是如此。例如,损坏您自己的堆栈指针会使您崩溃,甚至您的 POSIX 信号回调也看不到它。
【讨论】:
【参考方案6】:如果你愿意,你可以自己检查指针并抛出......
if (p == nullptr) throw std::exception("woot! a nullptr!")
p->foo();
所以当然这只是为了调试问题,nullptr 不应该首先出现:)
【讨论】:
【参考方案7】:C++ 中不存在“空指针异常”之类的东西。您可以捕获的唯一异常是 throw
表达式显式抛出的异常(另外,正如 Pavel 所指出的,标准 operator new
、dynamic_cast
等本质上抛出的一些标准 C++ 异常)。 C++ 中没有其他例外。取消引用空指针、除以零等在 C++ 中不会产生异常,它会产生未定义的行为。如果您希望在这种情况下引发异常,您自己有责任手动检测这些情况并明确执行throw
。这就是它在 C++ 中的工作原理。
您似乎在寻找的其他任何东西都与 C++ 语言有关,而是特定实现的功能。例如,在 Visual C++ 中,系统/硬件异常可以“转换”为 C++ 异常,但这种非标准功能需要付出代价,通常不值得付出。
【讨论】:
"你可以捕获的唯一异常,是由 throw 表达式显式抛出的异常" - 和默认operator new
(它可以使用显式 throw
来实现,当然,但没有什么需要它这样做;这很可能是内在的)。
除了operator new
、dynamic_cast
之外,C++ 中还有更多地方可以抛出引用类型作为另一个示例,但这不是重点。关键是您只能捕获 C++ 异常。当然,声称它们只能由用户级别的throw
抛出是不正确的。
取消引用空指针/除以零不会产生未定义的行为。它抛出一个异常,区别在于它是一个操作系统异常,而不是一个 C++ 异常。
@Lotharyx:即使这仅适用于实际发生的取消引用。允许 C++ 编译器“预见”任何未定义行为的出现,并在可能导致空指针取消引用的情况下生成完全不相关的任意代码。这不是一个抽象的观点。众所周知,像 GCC 这样的编译器会优化他们的代码,假设“这永远不会发生,因为行为是未定义的”。
例如如果您的代码中有*p = 42
,编译器可以合法地假设指针p
从不为空,并在该假设下生成代码。与 SIGSEGV
的空指针取消引用相比,这很容易将代码驱动到一些完全不同且不可预测的未定义行为表现形式。【参考方案8】:
你不能。取消引用空指针是系统的事情。
在 Linux 上,操作系统会在您的应用程序中发出信号。查看csignal 了解如何处理信号。要“抓住”一个,您需要挂钩一个函数,在 SIGSEGV
的情况下将调用该函数。在这里,您可以尝试在优雅地终止程序之前打印一些信息。
Windows 使用structured-exception-handling。您可以使用instristics __try/__except
,如上一个链接中所述。我在编写的某个调试实用程序中使用函数_set_se_translator
(因为它与钩子非常匹配)。在 Visual Studio 中,确保已启用 SEH。使用该函数,您可以挂接一个函数,以便在系统在您的应用程序中引发异常时调用;在你的情况下,它会用EXCEPTION_ACCESS_VIOLATION
调用它。然后,您可以抛出异常并将其传播回来,就好像一开始就抛出了异常一样。
【讨论】:
使用_set_se_translator
时,还必须使用/EHa
。
我发现_set_se_translator
的用处有限,因为(来自msdn)...“在多线程环境中,每个线程都单独维护翻译器功能。每个新线程都需要安装自己的翻译器函数。因此,每个线程负责自己的翻译处理。_set_se_translator 特定于一个线程;另一个 DLL 可以安装不同的翻译函数。 ...因此,如果您不控制调用您的代码的所有线程的创建,您将无法使用它。话虽如此 boost.test 使用这个效果很好,但这是单线程的
@iain: boost.test 是如何使用它的?这很令人惊讶,因为 boost 适用于许多非 MS 平台。
@Joseph:虽然 boost 通常与平台无关,但如果这样做有意义的话,它确实倾向于在内部使用特定于平台的代码。在这种情况下,它会为非 Windows 平台创建一个空的 _set_se_translator 实现(参见 boost-1.37.0/include/boost/test/impl/execution_monitor.ipp)。这对于 boost.test 非常有效,它可以帮助在可用的 Windows 平台上进行调试,并在其他平台上正常工作。
@Joseph: ...我认为unix上也有特殊的信号处理程序,但我们倾向于在windows中捕获大多数此类问题。当我们在 windows 中开发并使用单元测试来确保我们不会破坏 unix 代码时。【参考方案9】:
没有独立于平台的方法来做到这一点。在 Windows/MSVC++ 下可以使用__try/__except
但无论如何我都不建议这样做。您几乎可以肯定无法从分段错误中正确恢复。
【讨论】:
他专门询问空指针取消引用,这当然是可以恢复的(因为它不会破坏内存等)。仍然不是一个好主意,但出于不同的原因。 确实,唯一让我难以恢复的异常是堆栈溢出。事实上,我做不到。我认为这是不可能的,因为抛出异常会使用太多堆栈,至少在 Windows 上是这样(Linux 为异常提供了备用堆栈)。不过,您确实有足够的时间来创建一个新线程并尝试保存尽可能多的信息。 我不同意它是可恢复的,因为它代表程序中的逻辑错误,修复逻辑错误的唯一方法是更改程序本身。它是可捕获的,但可捕获并不意味着可恢复。如果您的程序检测到诸如此类的逻辑错误,它应该尽早崩溃,经常崩溃。【参考方案10】:正如其他人所说,你不能在 C++ 中做到这一点。
如果我能提出更广泛的观点:即使在一种允许您捕捉到它的语言中,更好的做法是不要接触空指针。当错误已经在你面前被炸毁时发现它,然后决定继续前进,就像它没有发生一样,这不是一个好的编码策略。诸如空指针取消引用、堆栈溢出等事件应被视为灾难性事件并应予以防御性避免,即使您的语言允许您对它做出不同的反应。
【讨论】:
【参考方案11】:取消引用 null(或超出数组末尾的指针,或随机无效指针)会导致未定义的行为。没有可移植的方式来“捕捉”它。
【讨论】:
【参考方案12】:C++ 不做指针检查(虽然我想有些实现可以)。如果您尝试写入空指针,它很可能会严重崩溃。它不会抛出异常。如果你想捕捉到这一点,你需要在尝试写入之前自己检查指针的值。
【讨论】:
这真的取决于实现和操作系统。例如。在 Win32 上,取消引用空指针将导致访问冲突(就像任何其他具有内存保护的平台一样),但 Win32 AV 是结构化异常,它可以被在顶部实现自己的异常模型的 C++ 编译器捕获Win32 SEH - 例如,带有/EHa
编译标志的VC++。当然,它仍然是邪恶的(并且不可移植)。
说到内存保护,我开始在 DOS 上写 C,写空指针通常意味着你要重启你的机器。至少 DOS 启动得相当快。
@Nate C-K:我曾经在 DOS 上的 C 程序中弄乱了一些指针,然后在挂起计算机之前打印了 ROM 的内容。或者至少我当时是这样解释我在屏幕上看到的。
正如你所说的“一些实现”。我想抛出空指针异常的语言(例如java)总是会检查0。虽然这是一个很大的开销,但速度比较会很好。
@TheTrowser:我怀疑开销很小,而且还有很多地方可以优化检查。以上是关于如何捕捉空指针异常? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
setText时的空指针异常 - TextView [复制]