在 C 或 C++ 中,我应该检查指针参数是否为 NULL/nullptr?
这个问题的灵感来自这个答案。
我一直认为,当调用者做了一些愚蠢的事情(例如传递无效参数)时,被调用者永远不会承担责任。我得出这个结论有几个原因,但也许最重要的一个来自 这篇文章:
一切未定义的都是未定义的。
如果一个函数在它的文档中没有说明传递 nullptr 是有效的,那么你最好不要将 nullptr 传递给该函数。我认为处理此类事情不是被调用者的责任。
然而,我知道会有一些人不同意我的观点。我很好奇我是否应该检查这些东西,以及为什么。
This question was inspired by this answer.
I've always been of the philosophy that the callee is never responsible when the caller does something stupid, like passing of invalid parameters. I have arrived at this conclusion for several reasons, but perhaps the most important one comes from this article:
Everything not defined is undefined.
If a function doesn't say in it's docs that it's valid to pass nullptr
, then you damn well better not be passing nullptr
to that function. I don't think it's the responsibility of the callee to deal with such things.
However, I know there are going to be some who disagree with me. I'm curious whether or not I should be checking for these things, and why.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
如果您要检查 NULL 指针参数,而您尚未签订接受和解释它们的合同,请使用
assert
来执行此操作,而不是使用条件错误返回。这样,调用方中的错误将立即被检测到并可以修复,并且可以轻松地禁用生产构建中的开销。我质疑断言的价值,除了作为文档;取消引用 NULL 指针导致的段错误对于调试同样有效。如果您向调用者返回错误代码,而该调用者已经证明自己有错误,则最有可能的结果是调用者将忽略该错误,并且当最初的原因出现时,糟糕的事情将会在稍后发生。错误的发生变得很难或不可能追踪。为什么可以合理地假设调用者会忽略您返回的错误?因为调用者已经忽略了
malloc
或fopen
或其他一些返回NULL
的特定于库的分配函数的错误返回> 表示错误!If you're going to check for NULL pointer arguments where you have not entered into a contract to accept and interpret them, do it with an
assert
, not a conditional error return. This way the bugs in the caller will be immediately detected and can be fixed, and it makes it easy to disable the overhead in production builds. I question the value of theassert
except as documentation however; a segfault from dereferencing the NULL pointer is just as effective for debugging.If you return an error code to a caller which has already proven itself buggy, the most likely result is that the caller will ignore the error, and bad things will happen much later down the line when the original cause of the error has become difficult or impossible to track down. Why is it reasonable to assume the caller will ignore the error you return? Because the caller already ignored the error return of
malloc
orfopen
or some other library-specific allocation function which returnedNULL
to indicate an error!在 C++ 中,如果您不想接受 NULL 指针,那么不要冒险:接受引用。
In C++, if you don't want to accept NULL pointers, then don't take the chance: accept a reference instead.
虽然一般来说,我看不到检测公共 API 的 NULL 的价值(为什么是 NULL 而不是其他无效地址?),但我可能仍然会这样做,因为许多 C 和 C++ 程序员期望这种行为。
While in general I don't see the value in detecting NULL (why NULL and not some other invalid address?) for a public API I'd probably still do it simply because many C and C++ programmers expect such behavior.
纵深防御原则说是的。如果这是一个外部 API,那么这是完全必要的。否则,至少需要一个断言来帮助调试 API 的滥用。
您可以记录合同直到您脸色发青,但您无法在被调用者代码中防止对您的函数的不明智或恶意滥用。您必须做出的决定是滥用可能造成的成本是多少。
Defense in Depth principle says yes. If this is an external API then totally essential. Otherwise, at least an assert to assist in debugging misuse of your API.
You can document the contract until you are blue in the face, but you cannot in callee code prevent ill-advised or malicious misuse of your function. The decision you have to make is what's the likely cost of misuse.
在我看来,这不是责任的问题。这是一个鲁棒性的问题。
除非我完全控制调用者并且我必须针对哪怕是一分钟的速度改进进行优化,否则我总是检查 NULL。
In my view, it's not a question of responsibility. It's a question of robustness.
Unless I have full control on the caller and I must optimize for even the minute speed improvement, I always check for NULL.
我的理念是:应该允许您的用户犯错误,但您的编程团队不应该犯错误。
这意味着您应该检查无效参数(包括 NULL)的唯一位置是顶级用户界面。在用户可以向代码提供输入的任何地方,您都应该检查错误,并尽可能优雅地处理它们。
在其他地方,您应该使用 ASSERTS 来确保程序员正确使用这些函数。
如果您正在编写 API,那么只有顶级函数才应该捕获并处理错误输入。继续检查调用堆栈三层或四层深处的 NULL 指针是没有意义的。
My philosophy is: Your users should be allowed to make mistakes, your programming team should not.
What this means is that the only place you should check for invalid parameters including NULL, is in the top-level user interface. Everywhere the user can provide input to your code, you should check for errors, and handle them as gracefully as possible.
Everywhere else, you should use ASSERTS to ensure the programmers are using the functions correctly.
If you are writing an API, then only the top-level functions should catch and handle bad input. It is pointless to keep checking for a NULL pointer three or four levels deep into your call stack.
我非常倾向于“不要相信用户的输入不会炸毁你的系统”以及一般的防御性编程。由于我以前制作过 API,所以我见过库的用户传入空指针,然后导致应用程序崩溃。
如果它确实是一个内部库,并且我是唯一一个(或只有少数人)有能力使用它的人,那么只要每个人都同意遵守一般合同,我可能会减轻空指针检查。我不能相信广大用户群会遵守这一点。
I lean heavily on the side of 'don't trust your user's input to not blow up your system' and in defensive programming in general. Since I have made APIs in a past life, I have seen users of the libraries pass in null pointers and then application crashes result.
If it is truly an internal library and I'm the only person (or only a select few) have the ability to use it, then I might ease up on null pointer checks as long as everyone agrees to abide by general contracts. I can't trust the user base at large to adhere to that.
对于 C 和 C++ 来说,答案会有所不同。
C++ 有参考资料。传递指针和传递引用之间的唯一区别是指针可以为空。因此,如果被调用函数的编写者需要一个指针参数,并且在它为 null 时忘记做一些正常的事情,那么他就是愚蠢、疯狂或编写带有类的 C 语言。
不管怎样,这不是谁承担责任的问题。为了编写出好的软件,两个程序员必须合作,所有程序员都有责任 1° 避免需要这种决策的特殊情况,2° 当这种情况失败时,编写以明确且有记录的方式爆炸的代码,以帮助调试。
所以,当然,你可以指着调用者并嘲笑他,因为他搞砸了,“所有未定义的东西都是未定义的”,并且不得不花一个小时调试一个简单的空指针错误,但是你的团队浪费了一些宝贵的时间。
The answer is going to be different for C and C++.
C++ has references. The only difference between passing a pointer and passing a reference is that the pointer can be null. So, if the writer of the called function expects a pointer argument and forgets to do something sane when it's null, he's silly, crazy or writing C-with-classes.
Either way, this is not a matter of who wears the responsibility hat. In order to write good software, the two programmers must co-operate, and it is the responsibility of all programmers to 1° avoid special cases that would require this kind of decision and 2° when that fails, write code that blows up in a non-ambiguous and documented way in order to help with debugging.
So, sure, you can point and laugh at the caller because he messed up and "everything not defined is undefined" and had to spend one hour debugging a simple null pointer bug, but your team wasted some precious time on that.
我支持防御性编程。
除非您可以分析这些
nullptr
检查发生在应用程序的瓶颈中...(在这种情况下,可以想象,不应该在这些点进行这些指针值测试),但总而言之,比较
int
与0
确实是一个便宜的操作。我认为让潜在的崩溃错误而不是消耗如此少的 CPU 是一种耻辱。
所以:测试你的指针是否为空!
I am pro defensive programming.
Unless you can profile that these
nullptr
checkings happen in a bottleneck of your application... (in such cases it is conceivable one should not do those pointers value tests at those points)but all in all comparing an
int
with0
is really cheap an operation.I think it is a shame to let potential crash bugs instead of consuming so little CPU.
so: Test your pointers against NULL!
我认为您应该努力编写对于每种可以想象的情况都健壮的代码。将
NULL
指针传递给函数是很常见的;因此,您的代码应该检查它并处理它,通常是通过返回错误值。库函数不应使应用程序崩溃。I think that you should strive to write code that is robust for every conceivable situation. Passing a
NULL
pointer to a function is very common; therefore, your code should check for it and deal with it, usually by returning an error value. Library functions should NOT crash an application.对于 C++,如果您的函数不接受空指针,则使用引用参数。一般来说。
但也有一些例外。例如,包括我自己在内的许多人认为,当实际参数最自然地是指针时,尤其是当函数存储指针的副本时,使用指针参数更好。即使函数不支持空指针参数。
对无效论证的辩护在多大程度上取决于,包括它取决于主观意见和直觉。
干杯&呵呵,
For C++, if your function doesn't accept nullpointer, then use a reference argument. In general.
There are some exceptions. For example, many people, including myself, think it's better with pointer argument when the actual argument will most naturally be a pointer, especially when the function stores away of a copy of the pointer. Even when the function doesn't support nullpointer argument.
How much to defend against invalid argument depends, including that it depends on subjective opinion and gut-feeling.
Cheers & hth.,
您必须考虑的一件事是,如果某些调用者确实滥用了您的 API,会发生什么情况。在传递 NULL 指针的情况下,结果是明显的崩溃,所以不检查也没关系。任何误用对于调用代码的开发人员来说都是显而易见的。
臭名昭著的 glibc 崩溃完全是另一回事。这种滥用导致了对调用者来说实际上有用的行为,并且 API 几十年来一直保持这种状态。然后他们改变了它。
在这种情况下,API 开发人员应该使用断言或某种类似机制检查值。但你无法及时返回来纠正错误。哀嚎和切齿是不可避免的。 此处阅读所有相关内容。
One thing you have to consider is what happens if some caller DOES misuse your API. In the case of passing NULL pointers, the result is an obvious crash, so it's OK not to check. Any misuse will be readily apparent to the calling code's developer.
The infamous glibc debacle is another thing entirely. The misuse resulted in actually useful behavior for the caller, and the API stayed that way for decades. Then they changed it.
In this case, the API developers' should have checked values with an assert or some similar mechanism. But you can't go back in time to correct an error. The wailing and gnashing of teeth were inevitable. Read all about it here.
如果您不需要 NULL,则不要将参数设置为指针。
通过使用引用,您可以保证该对象不会为 NULL。
If you don't want a NULL then don't make the parameter a pointer.
By using a reference you guarantee that the object will not be NULL.
对无效或不存在的数据执行无效操作的人只应该让他的系统状态变得无效。
我认为期望输入的函数应该检查 NULL 这完全是无稽之谈。或者与此相关的任何其他价值。函数的唯一工作是根据其输入或范围状态执行任务,仅此而已。如果您没有有效的输入,或者根本没有输入,那么甚至不要调用该函数。此外,NULL 检查不会检测其他数百万个可能的无效值。你知道你会传递 NULL,那么为什么你还要传递它,在另一个带有参数传递的函数调用上浪费宝贵的周期,对某个指针进行函数内比较,然后再次检查函数输出是否成功。当然,如果是在 1982 年,当我 6 岁的时候,我可能就这么做了,但那些日子早已一去不复返了。
当然,公共 API 是有争议的。就像某些提供白痴检查的 DLL 一样。您知道,这些论点:“如果用户提供 NULL,您不希望应用程序崩溃。”多么无争议啊。首先是用户传递了虚假数据;这是一个明确的选择,仅此而已。如果有人觉得这就是质量,那么……我更喜欢扎实的逻辑和性能胜过这些东西。此外,程序员应该知道自己在做什么。如果他对特定范围内的无效数据进行操作,那么他就没有资格称自己为程序员。我认为没有理由为了支持此类用户而降低产品的性能、增加功耗,同时增加二进制大小,从而影响指令缓存和分支预测。
He who performas invalid operations on invalid or nonexisting data, only deserves his system-state to become invalid.
I consider it complete nonsense that functions which expect input should check for NULL. Or whatever other value for that matter. The sole job of a function is to do a task based on its input or scope-state, nothing else. If you have no valid input, or no input at all, then don't even call the function. Besides, a NULL-check doesn't detect the other millions and millions of possible invalid values. You know on forehand you would be passing NULL, so why would you still pass it, waste valuable cycles on yet another function call with parameter passing, an in-function comparison of some pointer, and then check the function output again for success or not. Sure, I might have done so when I was 6 years old back in 1982, but those days have long since gone.
There is ofcourse the argument to be made for public API's. Like some DLL offering idiot-proof checking. You know, those arguments: "If the user supplies NULL you don't want your application to crash." What a non-argument. It is the user which passes bogus data in the first place; it's an explicit choice and nothing else than that. If one feels that is quality, well... I prefer solid logic and performance over such things. Besides, a programmer is supposed to know what he's doing. If he's operating on invalid data for the particular scope, then he has no business calling himself a programmer. I see no reason to downgrade the performance, increase power consumption, while increasing binary size which in turn affects instruction caching and branch-prediction, of my products in order to support such users.
如果它不承担这个责任,它可能会产生不好的结果,比如取消引用 NULL 指针。问题是它总是隐含地承担这个责任。这就是为什么我更喜欢优雅的处理方式。
If it doesn't take this responsibility it might create bad results, like dereferencing
NULL
pointers. Problem is that it always implicitly takes this responsibility. That's why i prefer graceful handling.在我看来,执行合同是被调用者的责任。
如果被调用者不应该接受
NULL
,那么它应该断言
。否则,当被调用者收到
NULL
时,它应该表现良好。也就是说,它要么在功能上应该是无操作,返回错误代码,要么分配自己的内存,具体取决于您为其指定的合约。它应该做从调用者的角度来看最明智的事情。作为 API 的用户,我希望能够继续使用它而不会让程序崩溃;我希望至少能够恢复,或者在最坏的情况下优雅地关闭。
In my opinion, it's the callee's responsibility to enforce its contract.
If the callee shouldn't accept
NULL
, then it shouldassert
that.Otherwise, the callee should be well behaved when it's handed a
NULL
. That is, either it should functionally be a no-op, return an error code, or allocate its own memory, depending on the contract that you specified for it. It should do whatever seems to be the most sensible from the caller's perspective.As the user of the API, I want to be able to continue using it without having the program crash; I want to be able to recover at the least or shut down gracefully at worst.
这种方法的一个副作用是,当您的库因传递无效参数而崩溃时,您往往会受到指责。
没有比 Windows 操作系统更好的例子了。最初,微软的方法是消除许多针对虚假论点的测试。结果是一个更高效的操作系统。
然而,现实情况是无效的参数一直在传递。对于不合格的程序员,或者只是使用其他函数返回的值,预计不会是 NULL。现在,Windows 执行更多验证,因此效率较低。
如果您想让例程崩溃,则不要测试无效参数。
One side effect of that approach is that when your library crashes in response to being passed an invalid argument, you will tend to get the blame.
There is no better example of this than the Windows operating system. Initially, Microsoft's approach was to eliminate many tests for bogus arguments. The result was an operating system that was more efficient.
However, the reality is that invalid arguments are passed all time. From programmers that aren't up to snuff, or just using values returned by other functions there weren't expected to be NULL. Now, Windows performs more validation and is less efficient as a result.
If you want to allow your routines to crash, then don't test for invalid parameters.
开发时间和运行时性能的开销与您正在设计的 API 的稳健性之间存在权衡。
如果您要发布的 API 必须在调用例程的进程内运行,您不应该检查 NULL 或无效参数。在这种情况下,如果你崩溃了,客户端程序也会崩溃,使用你的 API 的开发人员应该改正他的方式。
但是,如果您提供将在其中运行客户端程序的运行时/框架(例如,您正在编写可以托管代码或操作系统的虚拟机或中间件),那么您应该绝对 检查所传递参数的正确性。您不希望您的程序因为插件的错误而受到指责。
Overhead of development time + runtime performance has a trade-off with the robustness of the API you are designing.
If the API you are publishing has to run inside the process of the calling routine, you SHOULD NOT check for NULL or invalid arguments. In this scenario, if you crash, the client program crashes and the developer using your API should mend his ways.
However, if you are providing a runtime/ framework which will run the client program inside it (e.g., you are writing a virtual machine or a middleware which can host the code or an operating system), you should definitely check of the correctness of the arguments passed. You don't want your program to be blamed for the mistakes of a plugin.
在这种情况下,我所说的法律责任和道德责任之间是有区别的。打个比方,假设你看到一个视力不佳的人正走向悬崖边缘,却漫不经心地没有意识到悬崖的存在。就你的法律责任而言,如果你没有警告他,而他继续行走,坠崖身亡,一般来说是不可能成功起诉你的。另一方面,你本来有机会警告他——你有能力拯救他的生命,但你故意选择不这样做。一般人往往会轻蔑地看待这种行为,认为你有道德责任去做正确的事。
这如何应用于当前的问题?很简单——被调用者对调用者的行为不“合法”负责,无论是愚蠢的还是其他的,例如传递无效的输入。另一方面,当事情出了问题并且发现在你的函数中进行简单的检查可以使调用者免于自己的愚蠢时,你最终将为所发生的事情分担一些道德责任。
当然,这里需要进行权衡,具体取决于支票的实际费用。回到这个类比,假设你发现同一个陌生人正在慢慢地走向世界另一边的悬崖,并且通过花费毕生积蓄飞往那里警告他,你可以拯救他。如果在这种特殊情况下你忽略了这样做,很少有人会完全严厉地评判你(为了这个类比的目的,我们假设电话还没有被发明)。然而,在编码方面,如果检查就像检查
NULL
一样简单,那么如果您未能这样做,那么您就是失职,即使这种情况的“真正”责任在于调用者。There is a distinction between what I would call legal and moral responsibility in this case. As an analogy, suppose you see a man with poor eyesight walking towards a cliff edge, blithely unaware of its existence. As far as your legal responsibility goes, it would in general not be possible to successfully prosecute you if you fail to warn him and he carries on walking, falls off the cliff and dies. On the other hand, you had an opportunity to warn him -- you were in a position to save his life, and you deliberately chose not to do so. The average person tends to regard such behaviour with contempt, judging that you had a moral responsibility to do the right thing.
How does this apply to the question at hand? Simple -- the callee is not "legally" responsible for the actions of the caller, stupid or otherwise, such as passing in invalid input. On the other hand, when things go belly up and it is observed that a simple check within your function could have saved the caller from his own stupidity, you will end up sharing some of the moral responsibility for what has happened.
There is of course a trade-off going on here, dependent on how much the check actually costs you. Returning to the analogy, suppose that you found out that the same stranger was inching slowly towards a cliff on the other side of the world, and that by spending your life savings to fly there and warn him, you could save him. Very few people would judge you entirely harshly if, in this particular situation, you neglected to do so (let's assume that the telephone has not been invented, for the purposes of this analogy). In coding terms, however, if the check is as simple as checking for
NULL
, you are remiss if you fail to do so, even if the "real" blame in the situation lies with the caller.