在C/C++中检查空指针

在最近的一次代码审查中,一位贡献者试图强制所有NULL对指针的检查都按以下方式执行:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
// Handle null-pointer error
}
else
{
// Proceed
}

而不是

int * some_ptr;
// ...
if (some_ptr)
{
// Proceed
}
else
{
// Handle null-pointer error
}

我同意他的方法更清楚一些,因为它明确地说“确保此指针不为空”,但我要反驳说,任何使用此代码的人都会理解,在if语句中使用指针变量是隐式地检查NULL。此外,我觉得第二种方法引入同类错误的可能性较小:

if (some_ptr = NULL)

这对于查找和调试来说绝对是一件痛苦的事情。

你更喜欢哪种方式,为什么?

450779 次浏览

if (foo)足够清晰。使用它。

我用过的大多数编译器至少会在if赋值时发出警告,而不会有进一步的语法糖,所以我不接受这种说法。也就是说,我在专业上使用过这两种产品,对其中任何一种都没有偏好。在我看来,== NULL显然更清楚。

根据我的经验,if (ptr)if (!ptr)形式的测试是首选。它们不依赖于符号NULL的定义。它们不会暴露意外分配的机会。它们清晰而简洁。

编辑:正如Soapbox在注释中指出的那样,它们与C++类(如auto_ptr)兼容,这些类是充当指针的对象,并提供到bool的转换,以实现这种习惯用法。对于这些对象,与NULL的显式比较将必须调用到指针的转换,这可能具有其他语义副作用,或者比bool转换所暗示的简单存在检查更昂贵。

我偏爱没有多余文字的代码。if (ptr != NULL)if (ptr)具有相同的含义,但以冗余的特殊性为代价。下一件合乎逻辑的事情是编写if ((ptr != NULL) == TRUE),而这种方式是疯狂的。C语言很清楚,由ifwhile等测试的布尔值具有非零值为真和零为假的特定含义。冗余并不能使其更加清晰。

我使用if (ptr),但这完全不值得争论。

我喜欢我的方式,因为它很简洁,尽管其他人说== NULL使它更容易阅读和更明确。我知道他们是从哪里来的,我只是不同意额外的东西让它变得更容易。(我讨厌宏观,所以我有偏见。)由你决定。

我不同意你的论点。如果在条件中没有收到分配的警告,则需要提高警告级别。就这么简单。(出于对一切美好事物的热爱,不要把它们换来换去。

注意,在C++0x中,我们可以做if (ptr == nullptr),对我来说,确实读起来更好。(再说一次,我讨厌宏。但nullptr是好的。)我仍然做if (ptr),只是因为它是我习惯的。

实际上,我使用两种变体。

在某些情况下,您首先检查指针的有效性,如果它为NULL,则只需返回/退出函数。(我知道这可能会导致讨论“一个函数是否应该只有一个出口点”)

大多数情况下,您检查指针,然后执行所需的操作,然后解决错误情况。结果可能是带有多个if的丑陋的X倍缩进代码。

我将从这一点开始:一致性是王道,决定没有代码库中的一致性重要。

在C++中

在C++中,NULL定义为00L

如果你读过C++程序设计语言Bjarne Stroustrup建议显式使用_ABC_0,以避免在进行赋值时出现NULL,我不确定他是否做了同样的比较,我已经有一段时间没有读这本书了,我想他只是做了if(some_ptr),没有明确的比较,但我对此很模糊。

原因是NULL宏具有欺骗性(几乎所有宏都是如此),它实际上是0文字,而不是其名称所暗示的唯一类型。避免使用宏是C++中的一般准则之一。另一方面,0看起来像整数,并且当与指针比较或分配给指针时它不是整数。就我个人而言,我可以选择任何一种方式,但通常我会跳过明确的比较(尽管有些人不喜欢这一点,这可能是为什么你有一个贡献者建议改变)。

不管个人感受如何,这在很大程度上是一种最不邪恶的选择,因为没有一种正确的方法。

这很清楚,也是一个常见的习惯用法,我更喜欢它,在比较过程中不会有意外赋值的机会,它读起来很清楚:

if (some_ptr) {}

如果您知道some_ptr是指针类型,这一点就很清楚了,但它也可能看起来像整数比较:

if (some_ptr != 0) {}

这很清楚,在一般情况下是有道理的……但它是一个有漏洞的抽象,NULL实际上是0的文字,最终很容易被误用:

if (some_ptr != NULL) {}

C++11有nullptr,这是现在的首选方法,因为它是明确和准确的,只是要小心意外赋值:

if (some_ptr != nullptr) {}

在您能够迁移到C++0x之前,我认为担心您使用这些方法中的哪一个是浪费时间,它们都是不够的,这就是发明nullptr的原因(以及提出完美转发的泛型编程问题)。最重要的事情是保持一致性。

在C

C是一种不同的野兽。

在C_中,ABC_0可以定义为0((void *)0),C99允许实现定义的空指针常量。因此,它实际上涉及到NULL的实现定义,您必须在标准库中检查它。

宏是非常常见的,一般来说,它们经常被用来弥补语言中泛型编程支持和其他方面的不足。这种语言更简单,对预处理器的依赖也更普遍。

从这个角度来看,我可能会建议在C中使用NULL宏定义。

C程序设计语言(K&;R)会让您检查NULL==PTR以避免意外赋值。

这是这两种语言的基础之一,指针计算的类型和值可以用作控制表达式,_C++中的ABC_0和C中的int

就我个人而言,我总是使用if (ptr == NULL),因为它使我的意图明确,但在这一点上,它只是一个习惯。

使用=代替==将被任何具有正确警告设置的合格编译器捕获。

重要的一点是为你的团队选择一致的风格,并坚持下去。无论你走哪条路,你最终都会习惯它,在别人的代码中工作时摩擦的减少将是受欢迎的。

坦率地说,我不明白这有什么关系。任何一个都很清楚,任何对C或C++有一定经验的人都应该理解这两个。不过,有一条评论:

如果您计划识别错误并且不继续执行函数(即,您将立即抛出异常或返回错误代码),则应将其设置为保护子句:

int f(void* p)
{
if (!p) { return -1; }


// p is not null
return 0;
}

这样,您可以避免“箭头代码”。

如果风格和格式将成为你的评论的一部分,应该有一个商定的风格指南来衡量。如果有的话,按照风格指南所说的去做。如果没有,像这样的细节应该留在他们写的地方。这是对时间和精力的浪费,并且分散了代码审查真正应该发现的东西。说真的,如果没有风格指南,我会坚持原则上不更改这样的代码,即使它没有使用我喜欢的约定。

这并不重要,但我个人倾向于if (ptr)。对我来说,其含义甚至比if (ptr == NULL)更明显。

也许他是想说在快乐路径之前处理错误条件更好?在这种情况下,我仍然不同意评论者的观点。我不知道对此是否有公认的惯例,但在我看来,最“正常”的条件应该出现在任何if语句的第一位。通过这种方式,我可以更少地挖掘,以弄清楚这个函数是关于什么的,以及它是如何工作的。

例外情况是,如果错误导致我从函数中退出,或者我可以在继续之前从中恢复。在这些情况下,我首先处理错误:

if (error_condition)
bail_or_fix();
return if not fixed;


// If I'm still here, I'm on the happy path

通过预先处理异常情况,我可以处理它,然后忘记它。但是,如果我不能通过预先处理它来回到正确的路径上,那么应该之后(主要情况)来处理它,因为这样可以使代码更易于理解。在我看来。

但如果它不在风格指南中,那么它只是我的观点,你的观点也是有效的。要么标准化,要么不标准化。不要让一个评论家仅仅因为他有一个观点就伪标准化。

  • 指针不是布尔值
  • 现代的C/C++编译器会在您意外编写if (foo = bar)时发出警告。

因此我更喜欢

if (foo == NULL)
{
// null case
}
else
{
// non null case
}

if (foo != NULL)
{
// non null case
}
else
{
// null case
}

然而,如果我要写一套风格指南,我不会把这样的东西放进去,我会把这样的东西放进去:

确保对指针进行空检查。

我非常喜欢C/C++在ifforwhile语句中不检查布尔条件中的类型这一事实。我总是使用以下方法:

if (ptr)


if (!ptr)

甚至在整数或其他转换为bool的类型上:

while(i--)
{
// Something to do i times
}


while(cin >> a >> b)
{
// Do something while you've input
}

对我来说,这种风格的代码更具可读性,也更清晰。只是我个人的看法。

最近,在使用OKI 431微控制器时,我注意到以下情况:

unsigned char chx;


if (chx) // ...

比.更有效

if (chx == 1) // ...

因为在后面的情况下,编译器必须将CHx的值与1进行比较。其中CHx只是一个真/假标志。

还有

一点支持foo == NULL实践: 如果foo是例如int *bool *,则if (foo)检查可能被读取器意外地解释为测试指针对象的值,即,被解释为if (*foo)。这里的NULL比较提醒我们,我们讨论的是指针。

但我想一个好的命名惯例会让这个论点变得毫无意义。