为什么Pascal禁止修改for block中的计数器?

时间:2010-01-15 13:40:25

标签: for-loop pascal loop-counter

是因为Pascal的设计是这样,还是有任何权衡?

或者禁止或禁止修改for-block内的计数器有什么优缺点?恕我直言,没有什么用于修改for-block内的计数器。

修改
你能提供一个我们需要在for-block中修改计数器的例子吗?

很难在wallyk的答案和cartoonfox的答案之间做出选择,因为两个答案都很好.Cartoonfox从语言方面分析问题,而wallyk从历史和现实世界分析问题无论如何,谢谢你的所有答案,我要特别感谢wallyk。

7 个答案:

答案 0 :(得分:13)

在编程语言理论(和可计算性理论)中 WHILE和FOR循环具有不同的理论属性

  • WHILE循环可能永远不会终止(表达式可能只是TRUE)
  • FOR循环执行的有限次数应该在开始执行之前已知。 你应该知道FOR循环总是终止。

C中存在的FOR循环在技术上并不算作FOR循环,因为您不必知道循环在执行循环之前将迭代多少次。 (即你可以破解循环计数器永远运行)

使用WHILE循环可以解决的问题类别比使用Pascal中的严格FOR循环解决的问题严格得多。

Pascal以这种方式设计,以便学生有两种不同的循环结构,具有不同的计算属性。 (如果你实现了FOR-C-way,FOR循环只是while ...的替代语法。)

在严格的理论术语中,你不应该在for循环中修改计数器。如果你可以侥幸使用它,你只需要一个WHILE循环的替代语法

您可以在这些CS讲义中找到有关“while循环可计算性”和“for循环可计算性”的更多信息:http://www-compsci.swan.ac.uk/~csjvt/JVTTeaching/TPL.html

另一个这样的属性btw是在for循环之后loopvariable未定义。这也使得优化更容易

答案 1 :(得分:11)

Pascal最初是为CDC Cyber​​-a 1960年代和1970年代的大型机实现的 - 与今天的许多CPU类似,它具有出色的顺序指令执行性能,但对分支机构也有显着的性能损失。网络架构的这个和其他特征可能严重影响了Pascal的for循环设计。

简答题是允许分配循环变量需要额外的保护代码和对循环变量的混乱优化,这通常可以在18位索引寄存器中很好地处理。在那些日子里,由于硬件费用和无法以其他方式加速,软件性能受到高度重视。

长答案

Control Data Corporation 6600系列包括Cyber​​,是一种RISC架构,使用由18位地址引用的60位中央存储器字。有些型号有一个(昂贵的,因此不常见的)选项,比较移动单元(CMU),用于直接寻址6位字符字段,但是不支持任何类型的“字节”。由于CMU一般不能计算在内,因此大多数Cyber​​代码都是因为缺少而生成的。每个单词十个字符是通常的数据格式,直到支持小写字符让位于一个试验性的12位字符表示。

指令是15位或30位长,但CMU指令实际上是60位长。因此,每个字中最多包含4个指令,或者两个30位,或者一对15位和一个30位。 30位指令不能跨越字。由于分支目的地可能只引用单词,因此跳转目标是字对齐的。

架构没有堆栈。实际上,过程调用指令RJ本质上是不可重入的。 RJ通过在RJ指令所在的位置写入跳转到下一条指令来修改被调用过程的第一个字。被叫程序通过跳转到它们的开头返回给调用者,这是为返回链接保留的。程序从第二个字开始。为了实现递归,大多数编译器都使用了辅助函数。

寄存器文件有三种寄存器,每种都有8个实例,A0..A7用于地址操作,B0..B7用于索引,X0..X7用于通用算术。 A和B寄存器是18位; X寄存器是60位。设置A1到A5的副作用是将相应的X1到X5寄存器加载到加载的地址的内容中。设置A6或A7将相应的X6或X7内容写入加载到A寄存器的地址。 A0和X0未连接。 B寄存器几乎可以在每个指令中用作从任何其他A,B或X寄存器中加或减的值。因此它们非常适合小型计数器。

对于有效代码,B寄存器用于循环变量,因为可以在它们上使用直接比较指令(B2 <100等);与X寄存器的比较仅限于零关系,因此将X寄存器与100进行比较,例如,需要减去100并测试结果小于零等。如果允许对循环变量赋值,则为60位值在分配给B寄存器之前必须进行范围检查。这真是一件麻烦事。 Herr Wirth可能认为麻烦和低效都不值得实用 - 程序员在这种情况下总是可以使用whilerepeat ... until循环。 / p>

额外的怪异

几种独特的Pascal语言功能直接与Cyber​​的各个方面相关:

  • pack关键字:要么单个“字符”占用60位字,要么每个字打包10个字符。
  • (不寻常)alfa类型:packed array [1..10] of char
  • 内部程序pack()unpack()来处理压缩字符。它们不对现代体系结构进行任何转换,只进行类型转换。
  • text文件与file of char
  • 的奇怪之处
  • 没有明确的换行符。使用writeln
  • 显式调用了记录管理
  • 虽然set of char在CDC上非常有用,但由于其过多的内存使用(8位ASCII的32字节变量/常量),它在许多后续的8位机器上都不受支持。相比之下,单个Cyber​​词可以通过省略换行和其他内容来管理原生的62个字符集。
  • 完整表达评估(与快捷方式相对)。这些不是通过跳转和设置一个或零来实现的(如今大多数代码生成器所做的那样),而是通过使用实现布尔算术的CPU指令来实现。

答案 2 :(得分:7)

Pascal最初被设计为一种鼓励块结构编程的教学语言。 Kernighan(K&K的K)撰写了一篇关于Pascal限制的论文({3}}。

禁止修改Pascal调用for循环的控制变量的内容,加上缺少break语句意味着可以知道有多少在不研究其内容的情况下执行循环体的次数。

没有break语句,并且在循环终止后无法使用控制变量更多的是限制而不是无法修改循环内的控制变量,因为它会阻止某些字符串和数组处理算法来自“明显”的方式。

Pascal和C之间的这些和其他区别反映了它们最初设计的不同理念:Pascal强制执行“正确”设计的概念,C允许或多或少的任何东西,无论多么危险。

(注意:Delphi确实有一个Break语句,以及ContinueExit,它与C中的return类似。)

显然,我们从不需要能够在for循环中修改控制变量,因为我们总是可以使用while循环重写。在C中使用这种行为的示例可以在K&amp; R第7.3节中找到,其中引入了printf()的简单版本。处理格式字符串'%'中的fmt个序列的代码是:

for (p = fmt; *p; p++) {
    if (*p != '%') {
        putchar(*p);
        continue;
    }
    switch (*++p) {
    case 'd':
        /* handle integers */
        break;
    case 'f':
        /* handle floats */
        break;
    case 's':
        /* handle strings */
        break;
    default:
        putchar(*p);
        break;
    }
}

虽然这使用指针作为循环变量,但它同样可以用字符串中的整数索引写入:

for (i = 0; i < strlen(fmt); i++) {
    if (fmt[i] != '%') {
        putchar(fmt[i]);
        continue;
    }
    switch (fmt[++i]) {
    case 'd':
        /* handle integers */
        break;
    case 'f':
        /* handle floats */
        break;
    case 's':
        /* handle strings */
        break;
    default:
        putchar(fmt[i]);
        break;
    }
}

答案 3 :(得分:3)

它可以使一些优化(例如循环展开)更容易:不需要复杂的静态分析来确定循环行为是否可预测。

答案 4 :(得分:1)

来自For loop

  

在某些语言(不是C或C ++)中   循环变量是不可变的   循环体的范围,任何   试图修改其价值   被视为语义错误。这样   修改有时是一个   程序员错误的后果,   这可能很难   识别一次。但是只是明显的   可能会检测到更改   编译器。情况所在   传递循环变量的地址   作为子程序的参数来实现它   很难检查,因为   例行公事的行为一般   编译器不可知。

所以这似乎是为了帮助你以后不要烧手。

答案 5 :(得分:1)

免责声明:自从我上次做PASCAL以来已经有几十年了,所以我的语法可能不完全正确。

你必须记住PASCAL是Nicklaus Wirth的孩子,而Wirth在设计PASCAL(及其所有继承者)时非常关注可靠性和可理解性。

考虑以下代码片段:

FOR I := 1 TO 42 (* THE UNIVERSAL ANSWER *) DO FOO(I);

不看程序FOO,回答以下问题:这个循环是否会结束?你怎么知道的?程序FOO在循环中调用了多少次?你怎么知道的?

PASCAL禁止修改循环体中的索引变量,以便能够知道这些问题的答案,并且知道当过程FOO发生变化时,答案不会改变。

答案 6 :(得分:1)

可以安全地得出结论,Pascal旨在防止在循环内修改for循环索引。值得注意的是,Pascal绝不是阻止程序员这样做的唯一语言,Fortran是另一个例子。

以这种方式设计语言有两个令人信服的理由:

  1. 程序,特别是其中的for循环,更易于理解,因此更易于编写,修改和验证。
  2. 如果编译器知道通过循环的行程计数在进入循环之前建立并且之后不变,则循环更容易优化。
  3. 对于许多算法,此行为是必需的行为;例如,更新数组中的所有元素。如果内存服务Pascal还提供do-while循环和repeat-until循环。大多数,我想,用C风格语言实现的算法,修改循环索引变量或突破循环,可以很容易地用这些替代形式的循环实现。

    我已经摸不着头脑,没有找到允许在循环内修改循环索引变量的令人信服的理由,但后来我一直认为这样做是为了设计糟糕,并且选择了正确的循环结构作为良好设计的元素。

    此致

    马克