C: 与NULL的比较

28

不考虑宗教论据:

  • 选项一:

    if (pointer[i] == NULL) ...
    
    if (!pointer[i]) ...  
    

在C语言中,选项1和选项2在功能上是等价的吗?

由于没有比较操作,后者是否更快地解析?


6
假设“指针”实际上是一个指针数组,这样安全吗?如果您去掉“[i]”,可能会使问题更清晰。 - Steve Melnikoff
Sudo,你需要冷静一点。你因为一个诚实的回答而对jalf大喊大叫。你甚至问“这些是否具有相同的性能”或者“它们是否相同”,这表明你需要开放自己的思维,不要假设一切都是攻击。 - GManNickG
11个回答

35

我更喜欢显式的样式(第一种版本)。这样可以明确指针参与而不是整数或其他类型,但这只是风格问题。

就性能而言,两种方式应该没有区别。


31

等价的。语言标准上就是这么说的。而且人们对于宗教信仰有着非常执着的偏好!


3
这句话的意思是“应该用damnedest这个词吗?” - milesma

23

我喜欢第二种,其他人喜欢第一种。

实际上,我更喜欢第三种而不是第一种:

if (NULL == ptr) {
   ...
}

因为这样我:

  • 不会错过,只输入一个=
  • 如果条件很长(多行),就不会漏掉== NULL并将其误认为相反。

从功能上讲,它们是等效的。

即使NULL指针不是“0”(全部为零位),if (!ptr)也与NULL指针进行比较。

以下是错误的。 它仍然存在,因为有许多评论引用它: 不要将指针与字面值零进行比较。 它几乎可以在任何地方使用,但我IRC中未定义行为。


3
不要将指针与字面值零进行比较。虽然这在大多数情况下都可以正常工作,但据我所知,它是未定义的行为。 <- 不是这样的。你怎么能说它是未定义的行为? - Johannes Schaub - litb
8
托马斯,见 http://c-faq.com/null/ptrtest.html。将指针与文字0进行比较是完全安全的,但需要注意不改变原本的意思。 - Andrew Keeton
2
但是,NULL被定义为0。因此,如果您正在与NULL进行比较,则就编译器而言,您正在与0进行比较,因为编译器处理的不是您键入的内容,而是预处理器的输出,并且预处理器将使用0替换NULL。因此,在(void *)0处可能存在有效内容这一事实是无关紧要的。在编译器看到它之前,NULL将被替换为0。 - smcameron
2
@Dreamlax,你完全错了。你混淆了空指针值和空指针常量。当定义为0时,NULL始终有效。将其定义为0xFFFF当然是无效的。NULL是一个空指针常量。在C语言中,(void*)0也是一个例外。当转换为指针类型时,它变成了一个空指针值。这时它就成为了指向某个地址而不是0x0的指针。但这与将指针与0进行比较无关。这并不是将地址与0进行比较。整数不是地址。 - Johannes Schaub - litb
3
根据 C99 标准 6.3.2.3,"一个整数常量表达式值为0,或将这样的表达式转换为 void* 类型,被称为空指针常量。如果将空指针常量转换为指针类型,得到的指针称为空指针,保证与任何对象或函数的指针比较都不相等。" 这基本上说明 "在指针上下文中使用0就是NULL"。 - Evan Teran
显示剩余5条评论

19
通常情况下,假设编译器编写者至少具有最基本的智力是很有用的。你的编译器并不是由受伤的小鸭子编写的,它是由有多年编程经验和多年学习编译器理论的人编写的。这并不意味着你的编译器是完美的,总是最好的,但它确实能够完全处理微不足道的自动优化。
如果两种形式是等效的,那么为什么编译器不将其中一种翻译成另一种,以确保两种形式都同样高效?
如果if(pointer[i] == NULL)if(!pointer[i])慢,那么编译器不会将其更改为第二种更高效的形式吗?
因此,假设它们是等效的,它们是同样高效的。
至于问题的第一部分,是的,它们是等效的。语言标准实际上在某个地方明确声明了这一点——指针在非NULL时评估为true,在NULL时评估为false,所以两者完全相同。

8
我认为实际上是这样的。如果你对此感到冒犯,我很抱歉。我不是要攻击任何人,只是指出这一点,如果你停下来想一想,它应该是完全明显的。如果两个微不足道的语言结构意思相同,那么它们将以相同的方式编译。如果你想谈论居高临下,你觉得你有什么优势?你假设自己能在大约30秒内比编写编译器的经验丰富的开发人员进行更好的优化。对我来说,这似乎也很无礼。 ;) - jalf
8
如果你从来没有使用过由脑震荡的醉鸭子编写的编译器,那你可能还没有写软件很长时间;-)但幸运的是,自然选择已经处理掉了大部分这样的恐怖故事——但我认为还有一些存在...更重要的是:编译器编写者并不是神——他们像我们其他人一样容易犯错,会编写出有 bug 的代码。例如:我最近在处理一个 C++ 编译器/rtl,在测量 const 字符串的长度时每次都会调用复制构造函数... - Roddy

9
几乎肯定没有性能差异。虽然如此,我更喜欢第二种隐式风格。

3

NULL 应该在标准头文件中声明如下:

#define NULL ((void*)0)

因此,无论如何,您都在与零进行比较,并且编译器应该以相同的方式进行优化。每个处理器都有针对与零进行比较的一些“优化”或操作码。


2
实际上,对于C语言来说,它是#define NULL (void *)0 - avakar
糟糕,我的C/C++中的NULL弄反了。我会进行更新,感谢你指出来。 - Mark Rushakoff
2
C语言并没有规定NULL是((void*)0),在一些平台上它并不是这样。 - nos
通常情况下,它是((void*)0)。在Windows和Linux上肯定是这样的,可以合理地假设OP正在使用其中之一。如果他使用不同的平台,他可能不会问这个问题。 - Mark Rushakoff
2
C语言允许将NULL定义为0 - Johannes Schaub - litb

1

过早的优化是不好的。微小的优化也是不好的,除非你试图从CPU中挤出每一个Hz,否则没有必要这样做。正如人们已经展示的那样,编译器会自动优化大部分代码。

最好让你的代码尽可能简洁易读。如果这样更易读

if (!ptr)

比这个更好

if (NULL==ptr)

然后使用它。只要阅读你的代码的每个人都同意。
就个人而言,我使用完全定义的值 (NULL==ptr) ,这样清楚地知道我正在检查什么。可能输入更长一些,但我可以很容易地阅读它。如果读得太快,我会认为 !ptr 很容易被忽略掉。

1
不,过早的优化是不好的,尤其是如果你试图从CPU中挤出每一个赫兹。 - jalf
微观优化也不好,除非你有意图。 - shimpossible

0

打开编译器优化,它们基本上是相同的

在gcc 4.3.3上测试过

int main (int argc, char** argv) {
   char c = getchar();
   int x = (c == 'x');
   if(x == NULL)
      putchar('y');
   return 0;
}

对比

int main (int argc, char** argv) {
   char c = getchar();
   int x = (c == 'x');
   if(!x)
      putchar('y');
   return 0;
}


gcc -O -o test1 test1.c
gcc -O -o test2 test2.c


diff test1 test2

没有产生任何输出 :)


此外,传递gcc的-O0参数以禁用优化确实会产生不同的二进制文件。 - Mark Rushakoff
4
为什么人们会点赞这个回复?问题是关于测试指针,而这个例子测试的是整数。将整数与NULL进行测试是没有意义的;事实上,如果NULL被正确定义并且警告被打开,我怀疑您会得到编译器警告。 - Dipstick

0

这真的取决于编译器。如果大多数现代C编译器对您所描述的具体场景没有生成几乎相同的代码,我会感到惊讶。

让您的编译器为每个场景生成一个汇编清单,您就可以回答自己的问题(针对您特定的编译器 :))。

即使它们是不同的,性能差异在实际应用中也可能是无关紧要的。


-2

我进行了一次汇编转储,并发现了两个版本之间的差异:

@@ -11,8 +11,7 -
pushl %ecx
subl $20, %esp
movzbl -9(%ebp), %eax
- movsbl %al,%eax
- testl %eax, %eax
+ testb %al, %al

看起来后者实际上生成了一条指令,而第一个生成了两条指令,但这是相当不科学的。

这是gcc,没有优化:

test1.c:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{ 
  char *pointer[5];

if(pointer[0] == NULL) {
  exit(1);
}

exit(0);

}

test2.c: 将 pointer[0] == NULL 改为 !pointer[0]

gcc -s test1.c,gcc -s test2.c,diff -u test1.s test2.s


6
你的优化级别是多少,使用的编译器是什么? - Mark Rushakoff
5
为什么你要将 charNULL 进行比较?问题是关于指针的。(如果我看错了反汇编,请随意指出。) - avakar
1
很高兴看到,我读对了。 :) 打开优化,代码在两个测试中很可能是相同的。 - avakar
1
不仅使输出相同,使用-O2还会导致对exit(1)的简单调用,比较完全被优化掉。还要注意,您的片段存在未定义行为(访问未初始化的对象)。 - avakar
9
禁用优化后进行比较得到的结果为-1,这种做法是误导性和无用的。 - jalf
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接