ChatGPT解决这个技术问题 Extra ChatGPT

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

关闭。这个问题是基于意见的。它目前不接受答案。想改进这个问题?更新问题,以便可以通过编辑这篇文章用事实和引用来回答它。 7年前关闭。改进这个问题

在最近的一次代码审查中,一位贡献者试图强制以下列方式对指针执行所有 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)

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

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

拥有一致的风格有时比选择哪种风格更重要。
@Mark:一致性始终是您风格中最重要的因素。
“另外我觉得第二种方法引入同类错误的可能性较小:if(some_ptr = NULL)”这就是为什么有些人使用if(NULL == some_ptr),不能分配给NULL所以你会得到编译错误。
现在大多数编译器都警告条件中的赋值——不幸的是,太多的开发人员忽略了编译器的警告。
我不同意上面的说法,即一致性是最重要的。这与一致性无关;反正不是好的那种。这是我们应该让程序员表达自己风格的领域。如果有人不能立即理解这两种形式,那么他们应该立即停止阅读代码并考虑转行。我要说的远不止这些:如果你不能通过脚本自动强制执行外观一致性,那么将其删除。消耗人类道德的代价比人们在会议上做出的愚蠢决定要重要得多。

R
RBerteig

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

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

我喜欢在没有不需要文本的情况下说明其含义的代码。 if (ptr != NULL)if (ptr) 具有相同的含义,但以冗余特异性为代价。下一个合乎逻辑的事情是写 if ((ptr != NULL) == TRUE),这样就很疯狂。 C语言很清楚,通过ifwhile等测试的布尔值具有特定含义,非零值为真,零为假。冗余并没有使它更清楚。


此外,它们与通常覆盖 operator bool(或 safe_bool)的指针包装类(shared_ptr、auto_ptr、scoped_tr 等)兼容。
+1 此外,甚至 Stroustrup 在他的书的第 3 版中也建议使用 0 而不是 NULL(即使在分配中)来进行更好的类型检查。
需要注意的一件重要事情:直到最近,我还是赞成写“if (ptr)”而不是“if (ptr != NULL)”或“if (ptr != nullptr)”,因为它更简洁,所以代码更具可读性。但我刚刚发现,在最近的编译器上与 NULL/nullptr 进行比较时,比较会引发(resp.)警告/错误。所以如果你想检查一个指针,最好用“if (ptr != NULL)”而不是“if (ptr)”。如果您错误地将 ptr 声明为 int,则第一次检查将引发警告/错误,而后者将在没有任何警告的情况下编译。
@David天宇Wong 不,不是这个意思。该页面上的示例是两行序列 int y = *x; if (!x) {...},其中允许优化器推断,因为如果 x 为 NULL,则 *x 将未定义,因此它不能为 NULL,因此 !x 必须为 FALSE。表达式 !ptrnot 未定义的行为。在示例中,如果测试是在任何使用 *x 之前进行的,那么优化器将错误地消除测试。
Bjarne Stroustrup(C++ 的创建者)在他的官方 C++ 指南中同意这个答案:isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-if。规则:ES.87:不要在条件中添加多余的 == 或 !=。查看详细信息和链接上的几个例子。
w
wilx

if (foo) 很清楚。用它。


D
Deduplicator

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

在 C++ 中

NULL 在 C++ 中定义为 00L

如果您读过C++ 编程语言Bjarne Stroustrup suggests using 0 explicitly to avoid the NULL macro when doing assignment,我不确定他是否也做过比较,我已经有一段时间没有读过这本书了,我想他只是做过{4 } 没有明确的比较,但我对此很模糊。

原因是 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 中 NULL 可以定义为 0((void *)0),C99 允许实现定义空指针常量。所以它实际上归结为 NULL 的实现定义,您必须在标准库中检查它。

宏非常常见,通常它们被大量用于弥补语言和其他事物中通用编程支持的不足。该语言更简单,对预处理器的依赖更普遍。

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


tl;博士,尽管您对 C++ 中的 0 是正确的,但它在 C:(void *)0 中具有意义。在 C++ 中,0 优于 NULL,因为类型错误可能是 PITA。
@Matt:但是 NULL 无论如何都是零。
@GMan:不在我的系统上:来自 linux/stddef.h#define NULL ((void *)0)
@Matt:在 C++ 中。您说 0 更可取,但 NULL 必须为零。
这是一个很好的观点,我忽略了它,我正在通过建议来改进我的答案。 ANSI C 可以将 NULL 定义为 ((void *)0),C++ 将 NULL 定义为 0。我没有直接搜索这个标准,但我的理解是在 C++ 中 NULL 可以是 0 或 0L。
G
GManNickG

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

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

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

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


希望额外 5 年的经验能改变你的想法 :) 如果 C++/C 有类似 if_exist() 的操作符,那么这将是有意义的。
J
James McNellis

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

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

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

    // p is not null
    return 0;
}

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


有人在此处指出kukuruku.co/hub/programming/i-do-not-know-c if(!p) 是未定义的行为。它可以工作或不工作。
@David天宇Wong如果您在该链接中谈论示例#2,则 if(!p) 不是未定义的行为,而是它之前的行。 if(p==NULL) 也会有完全相同的问题。
M
Mark Ransom

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

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

重要的是为您的团队选择一致的风格并坚持下去。不管你走哪条路,你最终都会习惯它,并且在使用其他人的代码时减少摩擦是受欢迎的。


D
Daniel Hershcovich

还有一点支持 foo == NULL 实践:如果 fooint *bool *,那么 if (foo) 检查可能会被读者意外解释为测试指针,即if (*foo)。此处的 NULL 比较提醒我们正在讨论指针。

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


如果读者对间接级别如此迷茫,我不确定他作为程序员是否存在安全隐患。
P
Peter Mortensen

The C Programming Language (K&R) 会让您检查 null == ptr 以避免意外分配。


K&R 还会问“什么是智能指针?” C++ 世界中的情况正在发生变化。
或者更确切地说,C++ 世界中的情况已经发生了变化”;)
K&R 在哪里声明(参考)?他们自己从不使用这种风格。在 K&R2 第 2 章中,他们甚至推荐 if (!valid) 高于 if (valid == 0)。
安定下来,伙计们。他说的是k&r,不是K&R。他们显然不那么出名,因为他们的首字母甚至没有大写。
大写只会让你慢下来
d
dwo

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

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

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


我个人讨厌使用一个出口点的箭头代码。如果我已经知道答案,为什么不退出?
@ruslik:在 C(或 C-with-classes 风格的 C++)中,具有多个返回会使清理变得更加困难。在“真正的”C++ 中,这显然不是问题,因为您的清理工作是由 RAII 处理的,但是一些程序员(出于或多或少的正当理由)停留在过去,或者拒绝或不允许依赖 RAII .
@jalf 在这种情况下,goto 可能非常方便。此外,在许多情况下,需要清理的 malloc() 可以替换为更快的 alloca()。我知道不推荐它们,但它们的存在是有原因的(对我来说,goto 的命名标签比混淆的 if/else 树要干净得多)。
alloca 是非标准且完全不安全的。如果它失败了,你的程序就会崩溃。没有恢复的机会,而且由于堆栈相对较小,很可能会失败。失败时,您可能会破坏堆并引入损害特权的漏洞。切勿使用 alloca 或 vlas,除非您对将要分配的大小进行了 tiny 限制(然后您不妨只使用普通数组)。
D
Darryl

如果样式和格式将成为您评论的一部分,则应该有一个商定的样式指南来衡量。如果有,请按照样式指南的说明进行操作。如果没有,像这样的细节应该在写的时候留下。这是浪费时间和精力,并且分散了代码审查真正应该发现的内容。说真的,如果没有样式指南,我原则上不会更改这样的代码,即使它不使用我喜欢的约定。

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

通过预先处理异常情况,我可以处理它,然后忘记它。但是如果我不能通过预先处理它来回到快乐的道路上,那么它应该在主要案例之后处理,因为它使代码更容易理解。在我看来。

但如果它不在风格指南中,那么这只是我的意见,你的意见同样有效。要么标准化,要么不标准化。不要让审阅者仅仅因为他有意见就伪标准化。


if 正文缺少 {}
J
Jens Gustedt

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


J
JeremyP

指针不是布尔值

当您意外编写 if (foo = bar) 时,现代 C/C++ 编译器会发出警告。

所以我更喜欢

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

或者

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

但是,如果我正在编写一套风格指南,我不会在其中添加这样的内容,我会添加如下内容:

确保对指针进行空检查。


确实,指针不是布尔值,但在 C 中,if 语句不采用布尔值:它们采用整数表达式。
@Ken:那是因为 C 在这方面被破坏了。从概念上讲,它是一个布尔表达式,(在我看来)应该这样对待。
某些语言具有仅测试 null/not-null 的 if 语句。某些语言的 if 语句仅测试整数的符号(三向测试)。我认为没有理由将 C 视为“损坏”,因为他们选择了您喜欢的不同概念。我讨厌 C 的很多地方,但这只是意味着 C 程序模型与我的心智模型不同,并不是说我们(我或 C)中的任何一个都坏了。
@Ken:布尔值在概念上不是数字或指针,没关系是哪种语言。
我没有说布尔值是数字或指针。我说没有理由坚持 if 语句应该或只能采用布尔表达式,并提供了反例,除了手头的例子。许多语言都采用布尔值以外的东西,而不仅仅是 C 的“零/非零”方式。在计算中,拥有一个(仅)接受布尔值的 if 语句是一个相对较新的发展。
D
Donotalo

我非常喜欢 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 只是一个真/假标志。


P
Peter Mortensen

我使用的大多数编译器至少会在 if 分配上发出警告,而无需进一步的语法糖,所以我不买那个论点。也就是说,我已经专业地使用了这两种方法,并且对任何一种都没有偏好。在我看来,== NULL 肯定更清晰。