在C / C ++中检查NULL指针[关闭]


153

在最近的代码审查中,贡献者试图强制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
}

我同意他的方法在某种意义上更为清晰,因为它明确地说“确保此指针不为NULL”,但我要反驳这一点,说的是从事此代码工作的任何人都将理解在指针中使用指针变量。if语句隐式检查NULL。我也觉得第二种方法引入类似漏洞的可能性较小:

if (some_ptr = NULL)

这只是查找和调试的绝对痛苦。

您喜欢哪种方式,为什么?


32
有时,拥有一致的风格比选择哪种风格更为重要。
Mark Ransom

15
@Mark:一致性始终是样式中最重要的因素。
2010年

13
“而且我觉得第二种方法引入ilk错误的机会较小:if(some_ptr = NULL)”这就是为什么有些人使用if(NULL == some_ptr)不能将其赋值给NULL的原因编译错误。
user122302's

14
如今,大多数编译器都警告有条件的赋值-不幸的是,太多的开发人员忽略了编译器的警告。
Mike Ellery 2010年

10
我不同意上面的说法,即一致性很重要。这与一致性无关。反正不是很好。这是我们应该让程序员表达自己的风格的领域。如果有人不能立即理解任何一种形式,那么他们应该立即停止阅读代码,并考虑转行。我要说的比这还多:如果存在化妆品一致性,则无法使用脚本自动执行,然后将其删除。排水人类道德的成本WAY比那些愚蠢的决定人们在会议上更重要。
wilhelmtell

Answers:


203

以我的经验,首选形式为if (ptr)或的测试if (!ptr)。它们不依赖于符号的定义NULL。他们不会暴露意外指派的机会。而且它们清晰而简洁。

编辑:正如SoapBox在注释中指出的那样,它们与C ++类兼容,例如auto_ptr,这些对象充当指针,并提供转换以bool完全启用此惯用语。对于这些对象,与的显式比较NULL必须调用对指针的转换,该转换可能具有其他语义副作用,或者比bool转换所暗示的简单存在检查更为昂贵。

我偏爱没有不需要的文本的代码,它表示什么意思。if (ptr != NULL)具有与之相同的含义,if (ptr)但以多余的特异性为代价。下一个合乎逻辑的事情是写作if ((ptr != NULL) == TRUE),那就是疯狂。C语言清楚的是一个布尔通过测试ifwhile或类似的具有特定的非零值意思是真和零是假的。冗余无法使其更加清晰。


23
此外,它们与指针包装器类(shared_ptr,auto_ptr,scoped_tr等)兼容,它们通常会覆盖运算符bool(或safe_bool)。
SoapBox

2
我之所以将其作为“答案”,是因为对auto_ptr的引用增加了讨论。写下问题后,很明显,这实际上比其他任何事物都更加主观,并且可能不适合堆栈溢出“答案”,因为根据情况,其中任何一个都是“正确的”。
布莱恩·大理石

2
@Bryan,我很尊重,如果出现“更好”的答案,请随时根据需要移动对勾标记。至于主观性,是的,它是主观的。但这至少是一个主观讨论,其中存在提出问题时并不总是显而易见的客观问题。线条模糊。和主观... ;-)
RBerteig

1
需要注意的重要一件事:直到最近,我还是赞成写“ if(ptr)”而不是“ if(ptr!= NULL)”或“ if(ptr!= nullptr)”,因为它更加简洁,因此该代码更具可读性。但是我刚刚发现,与最近的编译器上的NULL / nullptr进行比较,比较会产生(分别)警告/错误。因此,如果要检查指针,最好执行“ if(ptr!= NULL)”而不是“ if(ptr)”。如果错误地将ptr声明为int,则第一个检查将引发警告/错误,而后者将在没有任何警告的情况下进行编译。
2014年

1
@David天宇Wong不,那不是什么意思。该页面上的示例是两行序列int y = *x; if (!x) {...},其中允许优化器*x推断出,如果x为NULL,则由于未定义,因此它不能为NULL,因此!x必须为FALSE。表达!ptr不是不确定的行为。在该示例中,如果在使用之前进行了测试*x,则优化器消除测试是错误的。
RBerteig '17


25

我将从这一点开始:一致性为王,决策不如代码库中的一致性重要。

在C ++中

在C ++中,NULL定义为0或0L。

如果您已经阅读了C ++编程语言 Bjarne Stroustrup建议在进行赋值时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 ++ 0x具有nullptr,由于它明确且准确,现在是首选方法,请小心意外赋值:

if(some_ptr != nullptr){;}

在您能够迁移到C ++ 0x之前,我会担心这是浪费时间,因为您担心使用的是哪种方法,它们都不够用,这就是为什么发明nullptr的原因(以及完美的转发所带来的通用编程问题) 。)最重要的是保持一致性。

在C中

C是另一只野兽。

在C中,可以将NULL定义为0或((void *)0),C99允许实现定义的空指针常量。因此,它实际上归结为实现的NULL定义,您将必须在标准库中对其进行检查。

宏非常常见,并且通常会大量使用宏来弥补语言和其他方面的通用编程支持方面的不足。语言更简单,对预处理器的依赖更加普遍。

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


tl; dr,尽管您0在C ++中是对的,但它在C:中具有含义(void *)0。在C ++中0更可取NULL,因为类型错误可以是PITA。
马特·乔纳

@Matt:但是NULL还是零。
GManNickG

@GMan:不在我的系统上:#define NULL ((void *)0)linux/stddef.h
Matt Joiner 2010年

@Matt:在C ++中。您说0是可取的,但NULL必须为零。
GManNickG

这是一个好点,我忽略了提及,并且通过提出建议来改善我的答案。ANSI C可以将NULL定义为((void *)0),C ++可以将NULL定义为0。我没有为此直接定义标准,但是我的理解是,在C ++中,NULL可以为0或0L。
M2tM 2010年

19

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

我喜欢我的方式,因为它简洁明了,尽管其他人说它== NULL使它更易于阅读和更明确。我知道他们来自哪里,我只是不同意使事情变得简单的多余东西。(我讨厌宏,所以我有偏见。)由您决定。

我不同意你的说法。如果您没有收到有条件分配的警告,则需要提高警告级别。就那么简单。(出于对所有美好事物的热爱,请不要切换它们。)

请注意,在C ++ 0x中,我们可以做到if (ptr == nullptr),对我来说确实读得更好。(再次,我讨厌宏。但是nullptr很好。)if (ptr)尽管如此,我仍然这样做,只是因为这是我的习惯。


希望有5年以上的经验会改变您的想法:)如果C ++ / C具有类似if_exist()的运算符,那么这将是有道理的。
magulla

10

坦白说,我不明白为什么这么重要。任一个都非常清楚,并且任何对C或C ++有一定经验的人都应该两者都理解。不过,有一条评论:

如果您打算识别错误并且不继续执行该函数(即,您将引发异常或立即返回错误代码),则应使其成为保护子句:

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

    // p is not null
    return 0;
}

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


有人指出这里kukuruku.co/hub/programming/i-do-not-know-cif(!p)是不确定的行为。它可能行不通。
大卫·天宇·王

@David天宇Wong如果您在该链接上谈论的是示例2,那if(!p)不是未定义的行为,而是它的前一行。并if(p==NULL)会有完全相同的问题。
Mark Ransom

8

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

使用=代替==将任何主管编译器正确的警告设置捕获。

重要的一点是为团队选择一个一致的风格并坚持下去。不管您采用哪种方式,您最终都会习惯它,并且欢迎在使用他人代码时失去摩擦。


7

仅支持该foo == NULL做法的一点:如果foo是a int *或a bool *,那么if (foo)读者可能会意外地将支票解释为测试pointe的值,即as if (*foo)NULL这里的比较提醒我们,我们在谈论指针。

但是我想一个好的命名约定会使这种论点失去意义。


4

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

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

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


1
我个人讨厌使用一个出口点会导致的箭头代码。如果我已经知道答案了,为什么不退出?
ruslik 2010年

1
@ruslik:在C(或C类类C ++)中,具有多个返回值使清除工作更加困难。在“真实” C ++中,这显然不是问题,因为您的清理工作是由RAII处理的,但是某些程序员(出于或多或少有效的原因)被困在过去,要么拒绝,要么被禁止依赖RAII。 。
jalf

在这种情况下@jalf goto可能非常方便。同样,在许多情况下malloc(),可以将需要清除的a替换为更快的alloca()。我知道不推荐使用它们,但是它们存在是有原因的(对我来说,一个有名的标签goto比混淆的if/else树干净得多)。
ruslik 2010年

1
alloca是非标准且完全不安全的。如果失败,您的程序就会崩溃。没有恢复的机会,并且由于堆栈相对较小,因此很可能发生故障。失败时,可能会破坏堆并引入损害特权的漏洞。alloca除非您对要分配的大小有微小的限制,否则不要使用或vlas (然后您也可以只使用普通数组)。
R .. GitHub停止帮助ICE 2010年

4

C编程语言(K&R)会让您检查null == ptr,以避免意外分配。


23
K&R还会问“什么是智能指针?” C ++世界正在发生变化。
亚历克斯·埃梅利安诺夫

2
或者更确切地说,C ++世界已经发生了变化”;)
杰夫

3
K&R在何处声明(参考)?他们从不自己使用这种样式。在K&R2第2章,他们甚至建议if (!valid)以上if (valid == 0)。
2010年

6
大家安顿下来。他说k&r,不是K&R。由于它们的首字母甚至没有大写,因此它们显然不那么著名。
jmucchiello

1
大写只会减慢您的速度
Derek

3

如果样式和格式将成为您的评论的一部分,则应该有一个公认的样式指南作为参考。如果有,请按照样式指南的指示进行操作。如果没有,则应在编写时保留此类详细信息。这是浪费时间和精力,并且分散了代码审查真正应该发现的内容。严重的是,如果没有样式指南,我原则上将尽量不要更改这样的代码,即使它不使用我喜欢的约定也是如此。

但这并不重要,但我个人的喜好是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

通过预先处理异常情况,我可以解决它,然后再忽略它。但是如果我不能通过预先处理而回到幸福的道路上,那么应该在以后处理在主要情况因为这会使代码更易于理解。在我看来。

但是,如果它不在样式指南中,那么这只是我的观点,您的观点同样有效。要么标准化,要么不标准化。不要仅仅因为评论者有意见就让其伪标准化。


1

这是两种语言的基础知识之一,指针将评估为可bool在C ++和C中用作控件表达式的类型和值int。只需使用它即可。


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

所以我更喜欢

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

要么

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

但是,如果我正在编写一组样式准则,则不会在其中添加类似的内容,而会在其中添加以下内容:

确保对指针进行空检查。


2
的确,指针不是布尔值,但是在C语言中,if语句不采用布尔值:它们采用整数表达式。

@Ken:那是因为C在这方面是坏的。从概念上讲,它是一个布尔表达式,因此(我认为)应这样对待。
JeremyP

1
某些语言的if语句仅测试null / not-null。某些语言的if语句仅测试整数的符号(3向测试)。我认为没有理由认为C是“残破的”,因为他们选择了您喜欢的其他概念。我对C有很多讨厌的东西,但这仅意味着C程序模型与我的思维模型不同,不是我们(我或C)中的任何一个都坏了。
肯(Ken)

@Ken:从概念上讲,布尔值不是数字或指针,请不要介意哪种语言。
JeremyP

1
我不是说布尔值是数字或指针。我说过,没有理由坚持说if语句应该或只能采用布尔表达式,除了手头的语句外还提供了反例。许多语言都采用布尔值以外的方式,而不仅仅是采用C的“零/非零”方式。在计算中,具有仅接受布尔值的if语句是相对较新的发展。
肯(Ken)

1

我是一个事实,即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只是一个true / false标志。


1

我使用过的大多数编译器至少会在if没有进一步语法加糖的情况下警告分配,因此我不购买该参数。就是说,我既专业,也没有偏好。== NULL虽然在我看来,这个绝对清晰。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.