正文
许多人也许会注意到一个现象,那就是在一些现代编程语言(当然,并不是指“最近出现”的编程语言)中,自增和自减运算符被取消了。也就是说,在这些语言中不存在i
或j--
这样的表达,而是只存在i = 1
或j -= 1
这样的表达方式了。本回答将从设计哲学这个角度上探讨这一现象产生的背景与原因。
严格来说,说"i 正在消失"也许有失偏颇,因为主流编程语言中似乎只有Python、Rust和Swift不支持自增自减运算符。
当我第一次接触Python时,这也曾令我感到困惑。我曾经有兴趣地搜索了很多相关的回答和文章,但都没有得到满意的答案。如今数年过去了,我尝试重新思考这个问题,并给出我的答案。
请注意,本文仅“从设计哲学上”讨论这一问题,不会特别涉及语言本身的性质。例如在Python中,不提供自增自减运算符很大一部分原因是由于其整数类型为 Immutable 的,但这并不是“从设计哲学上”的讨论,因此本文不会包含相关内容。
为什么会存在自增自减运算符?
起源
维基百科指出,自增和自减运算符最早出现在B语言(即C的前身)中。B语言的发明者与C语言的发明者相同,也是K&R,其中Ken Thompson最早在B语言中引入了自增与自减运算符。因此也常常有人不太严谨地说“自增自减运算符最早起源于C”,事实情况虽然有些出入,但也差不了太多。
B语言的语法与C高度相似,最大的不同可能在于B是无类型的。不过,这里不太多介绍B语言,否则就偏离主题了。这里所要强调的只是自增自减运算符最早的起源。
关于为什么B语言中引入了自增自减运算符这个问题众说纷纭,Ken Thompson也从未公开表示过自己当初为何创建了这两个运算符。然而,有一个误解需要澄清,即这两个运算符的引入不可能是对应于汇编语言的INC
和DEC
指令。事实上,B语言的另一位创造者(当然,也是C语言的创造者)Dennis M. Ritchie曾在其回忆"The Development of the C Language"中指出:
……Thompson went a step further by inventing the
and
--
operators, which increment or decrement; their prefix or postfix position determines whether the alteration occurs before or after noting the value of the operand. They were not in the earliest versions of B, but appeared along the way. People often guess that they were created to use the auto-increment and auto-decrement address modes provided by the DEC PDP-11 on which C and Unix first became popular. This is historically impossible, since there was no PDP-11 when B was developed. The PDP-7, however, did have a few 'auto-increment' memory cells, with the property that an indirect memory reference through them incremented the cell. This feature probably suggested such operators to Thompson; the generalization to make them both prefix and postfix was his own. Indeed, the auto-increment cells were not used directly in implementation of the operators, and a stronger motivation for the innovation was probably his observation that the translation ofx
was smaller than that ofx=x 1
.
文中的说法有些模糊,仅指出自增自减运算符不可能是产生于PDP-11的auto-increment和auto-decrement地址模式(因为B语言发明时这台机器甚至都不存在),然而并未指出其是否对应于汇编语言中的INC
和DEC
。为了验证这一说法,我找到了文中提到的PDP-7的指令集,的确不包含INC
或DEC
指令。为了严谨起见,我还查了一下PDP-7的汇编手册,也没有找到相关指令。这证明了自增自减运算符的发明不可能是由于其直接对应于汇编语言中的INC和DEC指令。
顺带一提,为了考证INC和DEC汇编指令的最初出现时间,我找到了1969年版的PDP-11 Handbook, 其中指出了INC和DEC是在PDP-11中被新引入的汇编指令(截图中没包含DEC,但手册后面有包含这条指令):
PDP-11 Handbook, 1969, Page 34
PDP-11的正式发布时间是1970,而B语言的诞生时间是1969。除非Ken Thompson参与了PDP-11的早期开发工作,否则自增自减运算符的灵感不可能源于INC
和DEC
汇编指令。当然,正如Dennis Ritchie指出,早在PDP-7中就已经出现了auto-increment memory cells,很可能是它启发了Ken Thompson引入自增自减运算符。
另一个能够反驳“自增自减运算符直接对应于汇编指令”的事实是,B语言最初并不能直接编译成机器码,而是需要编译成一种被称作“线程码(threaded code)”的东西(原谅我找不到合适的翻译) 。既然最初都无法直接编译成机器码,那就更没有这种说法了。
所以说,自增自减运算符最初出现的原因可能非常简单——当年机器字节很珍贵,而 x能比x=x 1或x =1少写一点代码,在那时候能少写一点代码总是好的——于是自增自减运算符出现了。
提高程序运行效率?原子性?
好吧,虽然上面已经严肃地论证了自增自减运算符的出现与PDP-11的ISA没关系,但K&R不过是C的创始人,他们懂什么C语言(雾)?K&R之后C语言的各种语法都被玩出花来了,恐怕他们也想不到C语言后续的发展。自增自减运算符到底会不会被编译成INC
和DEC
,还得看现代的各种编译器。下面我在Ubuntu 22.04下将相关的C代码编译,然后反汇编,看看i
是否会被编译成INC
,以验证“自增自减运算符能够提高程序运行效率”的逻辑是否成立。
下面是测试程序:
// incr_test.c #include <stdio.h> int main(void) { for (int i = 0; i < 5; i ) { printf("%d", i); } return 0; }
然后运行gcc,默认不开启优化:
gcc -o incr_test incr_test.c
然后运行objdump反汇编:
objdump -d incr_test.c
下面展示相关汇编代码(我所使用的是x86-64平台),已剔除无关代码:
0000000000001149 <main>: 1149: f3 0f 1e fa endbr64 114d: 55 push %rbp 114e: 48 89 e5 mov %rsp,%rbp 1151: 48 83 ec 10 sub $0x10,%rsp 1155: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 115c: eb 1d jmp 117b <main 0x32> 115e: 8b 45 fc mov -0x4(%rbp),