C语言有前置增量和后置增量的历史原因是啥?
Posted
技术标签:
【中文标题】C语言有前置增量和后置增量的历史原因是啥?【英文标题】:What are the historical reasons C languages have pre-increments and post-increments?C语言有前置增量和后置增量的历史原因是什么? 【发布时间】:2015-08-06 10:15:48 【问题描述】:(注意:我不是在问前置增量与后置增量的定义,或者它们在 C/C++ 中的使用方式。因此,我不认为这是一个重复的问题。)
C 语言的开发者(Dennis Ritchie 等人)出于很好的理由创建了递增和递减运算符。我不明白的是为什么他们决定区分前后增量/减量?
我的感觉是,在开发 C 语言时,这些运算符比现在有用得多。大多数 C/C++ 程序员使用其中一种,而来自其他语言的程序员今天发现这种区别很奇怪和令人困惑(注意:这仅基于轶事证据)。
他们为什么决定这样做,以及在计算方面发生了什么变化,使得这种区别在今天不再那么有用?
为了记录,两者的区别可以看C++代码:
int x = 3;
cout << "x = 3; x++ == " << x++ << endl;
cout << "++x == " << ++x << endl;
cout << "x-- == " << x-- << endl;
cout << "--x == " << --x << endl;
将作为输出给出
x++ == 3
++x == 5
x-- == 5
--x == 3
【问题讨论】:
The Development of the C Language (by Ritchie) 包含一段关于增量运算符以及后缀/前缀的段落,但没有详细说明。 副作用才是有用的。 [推测警告] 在使用 C 或 C++ 进行系统编程时,您最终会编写大量代码。任何能帮助你保持简洁的东西都是好的。前缀和后缀运算符允许程序员浪费更少的空间来处理变量。我认为这就是他们添加它的原因。不过,我很惊讶 C 没有变量值交换运算符。 @Dai 当我开始学习 C 时,我会使用任何看起来 l33t 的技巧,今天我尊重 POLA 和其他各种原则。Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ~Martin Fowler
是什么让您说“计算中发生了哪些变化,这种区别在今天不再那么有用了”?对于缺少单个inc/dec
指令的处理器,它们可以轻松地替换为add r0,r0,#1
(ARM 示例)。 post 和 pre 之间的区别在今天和以往一样重要。
【参考方案1】:
当时的硬件广泛支持递增和递减 1:单个操作码和快速。这是因为“加 1”和“减 1”是代码中非常常见的操作(直到今天仍然如此)。
后减和前减形式仅影响此操作码在生成的机器代码中插入的位置。从概念上讲,这模仿了“使用结果增加/减少 before 或 after”。在一个语句中
i++;
没有使用“之前/之后”的概念(因此它与++i;
的作用相同),而是在
printf ("%d", ++i);
确实如此。这种区别如今与设计 C 语言时一样重要(这个特殊的习语是从它的前身“B”复制而来)。
来自The Development of the C Language
这个特性 [PDP-7 的“‘自动增量’记忆单元”] 可能向 Thompson [Ken Thompson,他设计了“B”,C 的前身] 建议了这样的运算符;使它们同时具有前缀和后缀的概括是他自己的。确实,自增单元并没有直接用于算子的实现,更强烈的创新动机可能是他观察到 ++x 的平移小于 x=x+1 的平移。
感谢@dyp 提及本文档。
【讨论】:
Ritchie, The Development of the C Language: “人们经常猜测它们是为了使用 DEC PDP-11 提供的自动递增和自动递减地址模式而创建的,其中 C Unix 首先流行起来。这在历史上是不可能的,因为在开发 B 时还没有 PDP-11。” PDP-7 似乎有一些可能起到了作用的特性,尽管我不认为从文档中可以完全清楚地看出,它们是同时存在前缀和后缀++
的主要原因。
the translation was smaller
。所以在这里你有你歇斯底里的葡萄干:早期编译器中缺少优化器。
B "actively" ran on a Honeywell 6070,至少在 1967 年左右有一个 AOS
(add one to storage) instruction。它没有类似的减法运算,neither did the PDP-7 其中B went next。当 C 遇到 PDP-11 时,DEC 团队已经介绍了auto-addressing modes。
鉴于早在 1967 年向存储添加一个是“事物”,并且大约在 1970 年之后可以使用完整的“寻址模式”,我怀疑社区普遍认可这些操作的价值低级(指令集)和高级(B、C)。
This related answer 有一段较长的摘录自《C 语言的发展》。【参考方案2】:
当你从n
倒数时,是前减还是后减很重要
#include <stdio.h>
void foopre(int n)
printf("pre");
while (--n) printf(" %d", n);
puts("");
void foopost(int n)
printf("post");
while (n--) printf(" %d", n);
puts("");
int main(void)
foopre(5);
foopost(5);
return 0;
请参阅code running at ideone。
【讨论】:
感谢分享这个智慧。我添加了对您帖子的引用。很可能我假设两个版本之间的差异是明显的错误,并且在我的回答中不知何故被夸大了。【参考方案3】:要获得超出推测的答案,很可能您必须亲自询问 Dennis Ritchie 等人。
除了已经给出的答案之外,我想补充一下我想出的两个可能的原因:
懒惰/节省空间:
您也许可以使用while(--i)
与while(i--)
等结构中的适当版本在输入文件中保存一些击键/字节。 (看看 pmg 的回答看看,为什么两者都会有所作为,如果你在第一次运行时没有看到它)
美学
出于对称性的原因,只有一个版本,无论是前置还是后置增量/减量,都可能会让人感觉缺少一些东西。
编辑:在提供的推测部分的输入文件中添加了一些字节,现在也提供了一个非常好的“历史”原因。
无论如何,整理这份清单的主要目的是提供一些可能的解释示例,这些解释不太具有历史意义,但今天仍然有效。
当然我不确定,但我认为询问个人品味以外的“历史”原因是从一个不一定正确的假设开始。
【讨论】:
很遗憾,要问丹尼斯,你需要一块占卜板——他 4 年前去世了。 mikyra,交出你的书呆子徽章。接下来你会告诉我们你不知道 Leonard Nimoy :-) 还有 John Nash...(但在创建 C(实际上是 B)时,“懒惰”可能不是重点,而是在 input 文件可能已经存在。) ...猫王呢?不要告诉我……好点反正我会把它添加到推测中。 ...无论如何,主要的一点是证明原因可能不是太具有历史意义,因为给出的两个原因,我今天仍然会错过这个功能,而是认为一种不支持它们的语言是奇怪的.【参考方案4】:对于 C
让我们看看 Kernighan & Ritchie 的原始理由(原始 K&R 第 42 和 43 页):
不寻常的方面是 ++ 和 -- 可以用作前缀或 作为后缀。 (...) 在不需要值的情况下 (..) 选择 前缀或后缀根据口味。但是在某些情况下 一个或另一个是特别需要的。
本文继续介绍了一些在索引中使用增量的示例,其明确目标是编写“更紧凑”的代码。所以这些运算符背后的原因是更紧凑的代码方便。
给出的三个示例(squeeze()
、getline()
和 strcat()
)仅在使用索引的表达式中使用后缀。作者将代码与不使用嵌入式增量的较长版本进行了比较。这证实了重点是紧凑性。
K&R 在第 102 页突出显示,这些运算符与指针解引用结合使用(例如 *--p
和 *p--
)。没有给出进一步的例子,但他们再次清楚地表明了紧凑性的好处。
对于 C++
Bjarne Stroustrup 希望具有 C 兼容性,因此 C++ 继承了前缀和后缀递增和递减。
但还有更多内容:在他的书“C++ 的设计和演变”中,Stroustrup 解释说,最初,他计划在用户定义的类中只为后缀和前缀都设置一个重载:
有几个人,尤其是 Brian Kernighan,指出这 从 C 的角度来看,限制是不自然的,并且阻止了用户 从定义一个可以用来替代 普通指针。
这导致他找到当前的签名差异来区分前缀和后缀。
顺便说一句,如果没有这些运算符,C++ 就不是 C++,而是 C_plus_1 ;-)
【讨论】:
【参考方案5】:考虑以下循环:
for(uint i=5; i-- > 0;)
//do something with i,
// e.g. call a function that _requires_ an unsigned parameter.
如果不将递减操作移到 for(...) 构造之外,则无法使用预递减操作复制此循环,最好将初始化、交互和检查全部集中在一个地方。
一个更大的问题是:一个类的增量运算符(全部 4 个)可能会过载。但是操作符是完全不同的:后操作符通常会生成类实例的临时副本,而前置操作符则不会。这是语义上的巨大差异。
【讨论】:
【参考方案6】:PDP-11 有一条指令对应于*p++
,另一条指令对应于*--p
(或者可能反过来)。
【讨论】:
请参阅The Development of the C Language (by Ritchie)。这些操作员在 B 中,这是 PDP-11 之前的版本。以上是关于C语言有前置增量和后置增量的历史原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章