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 示例)。 postpre 之间的区别在今天和以往一样重要。 【参考方案1】:

当时的硬件广泛支持递增和递减 1:单个操作码和快速。这是因为“加 1”和“减 1”是代码中非常常见的操作(直到今天仍然如此)。

后减和前减形式仅影响此操作码在生成的机器代码中插入的位置。从概念上讲,这模仿了“使用结果增加/减少 beforeafter”。在一个语句中

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语言有前置增量和后置增量的历史原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

C语言后置自增啥时候自增(自减)?

希尔排序算法(缩小增量排序)及C语言实现

c语言for循环无法正常执行?

C语言之希尔排序

c语言4

前置操作符和后置操作符(三十四)