这么多答jnz的小伙伴,jnz是x86的ISA,x86是1978年Intel搞出来的东西,C是1969到1973年在贝尔实验室被发明的,这不是「你爷爷姓李因为你爸爸姓李么?」
因此我们要去研究为啥C语言中认为0是假,其他都是真,就得回到上个世纪六七十年代,C被发明的时候。
当年贝尔实验室去设计C的主要目的,是用一个高级语言重写Unix。而C这个名字是从哪里来的呢?是因为他有个前身叫做B(现在已经死翘翘了)……B同样也是贝尔实验室发明C的大神发明的,也是在Unix上写东西的。而通过wiki(B (programming language))我们可以看到,在B语言里,就已经用了「0为假,其他为真」这个概念了。而这个概念继承给了C。
当然,B语言也有爸爸,B的爸爸叫做BCPL(现在也已经死翘翘了)。有趣的是,BCPL并没有用这个「0为假,其他为真」的概念!在BCPL里,假是全0,真是全1,从而得到了一个很有趣的结果就是「~False == True」。把假按位取反就是真,这看起来是个不错的性质。那如果if后面跟了一个不真不假的数呢?根据implementation来决定(勘误,这里用ub不是非常合适)。
追祖溯源到这里,我们应该已经基本确定,C中的这个约定,是来自于B语言。那我们就看看B语言发明时候的环境。B语言的发明是和Unix紧密相关的,而Unix当时运行在一个叫做PDP-7的电脑(勘误,这里随手就打上芯片了,PDP-7是个电脑)上(后来出了PDP-11,B语言遇到了瓶颈,才出现了C)。而这款芯片也是有自己的ISA的,其中有个很重要的命令叫作isz(index and skip if zero),这个命令被大量地用作branch。
我们知道,早期的程序语言,是比较讲究高级语言和assembly之间的对应的(这样compiler好写,也容易优化)。也正是早期assembly这种和0比较的语法存在,导致的B语言中出现了和0比较的这种现象,然后导致的C继承了(或者说也使用了)这种现象。
在PDP-11中,就出现了我们很熟悉的bne beq之类的assembly了,同样也是和0比较得出结果然后branch的。jnz那是十年之后的事情了~
你以为这就结束了么?当然没有~为什么无论是早期的assembly,还是近现代的assembly,都会有大量的和0比较的语法存在?和0比较有什么得天独厚的优势么?为什么不是1代表True,剩下都是False?为什么不是0到9代表False,剩下都是True?
这要从编码讲起。我们知道在计算机中,整数是以补码的形式被编码的。我们认为的数字0,就是每个bit都是0,而正整数是其二进制的表示形式,负整数的最高位是1,用补码表示。
让我们看回电路,我们知道任何「程序」的实现,最终都是要电路来完成的。我们有一个或者16位或者32位或者64位的「信号」(代表着我们想要比较的数),判断它是不是全是0,是要比判断它是不是一个特定的数(比如0xa5a5a5a5)要容易的!我们可以在输入端不用任何反向器的情况下去进行判断(比较极端的比如N-input NAND门)。而判断是否为一个特殊的数,是一定要多少对输入进行处理的(比较器或者反向器)。也就是说,在电路的角度看,判断一个N位二进制数是否是0,是比判断它是否是一个特殊的数,实现要更简单更快的(理论上判断是否都是1速度也很快)。
不仅如此,二进制补码带给我们的另外一个效应是,不光判断是否是0速度快,判断是否大于或者小于0,速度优势更明显!只需要判断一下符号位,然后再看除了符号位之外的那些是不是0就可以了。如果是大于等于或者小于等于,那就仅仅需要判断符号位!而做过一点逻辑电路的都知道,你想在RTL级去判断一个信号是否大于一个特殊的数,基本会被累死。那是一个非常麻烦的事情。
因此,从硬件的角度来看,和0去做比较,有着明显的优势。而在逻辑电路里0一般代表False,所以软件就自然地把数分为了0和非0的,也就衍生出了False和True。
总结一下,是硬件的特性,导致了判断一个多路信号和全0的关系更为简单和快速,又因为我们采用的编码,导致了assembly level上去比较一个寄存器和0的关系更加便捷,最后导致了高级语言C里的if语句,把0当作False,非0当作True。
当然,随着软件硬件越来越细分的功能,很多人开始对C的这个设计有所质疑。我个人是比较赞同C的这个设计在大规模high-level的工程中是弊大于利的。我觉得对于一个当代的高层语言,拥有一个完善的boolean变量是非常有价值的,可以减少很多无谓的错误。
但是我依然很喜欢C里的各种「设计缺陷」,这些「设计缺陷」其实带来了很多语法糖和有趣的性质。
if (ptr) { // I'm not a NULL pointer! } if (func1() | func2() | func3()) { // What's the difference? }
我想搞个笑
因为二进制里面除了0,其他的数都有1
C 语言是一种相对比较底层的语言,这个语言可以认为是试图用高级语言对汇编的元素进行复刻而生。
对于判断 0 或者非 0 两种状态,汇编有直接对应的指令(jnz跟jz指令),如果要判断是否等于 3 ,则需要额外的一条指令将目标减 3 然后再同 0 进行比较,这样指令数量增加了,也就耗费了额外的时钟周期。所以,C 作为一种相对贴近汇编的底层编程语言,if 命令只需要判断 0 跟非零就够了,它翻译成汇编最简洁。
早年间的始祖级程序员曾经教导弟子:条件判断尽量只跟 0 进行比较,效率最高。其原理正是因为早年间汇编的特性导致。
现在或者将来,或许会有其它的汇编架构,允许单指令直接与非零变量进行比较并跳转,但在 C 语言设计的当初,汇编必定是与 0 比较效率最高的。在那个程序员大都懂一点汇编的年代,做出 C 语言只与零比较这个决定,简直是天经地义的。
当然,C 语言中的 0 并非一直用来表示负面概念。对于 if 语句的判断来说, 0 为假,非零为真。但对于 C 语言函数的返回值来说,通常是返回 0 代表成功,返回非零代表错误代码。
所以,C 语言确实是只需要 0 跟非零两种状态判断,但并非永远用 0 代表负面的那个方向。
第一个不用看答案光看问题就被震惊了的问题。