跳转到内容

Oberon/ETH Oberon/faqlang

来自维基教科书,开放世界中的开放书籍

本文档最初托管在ETHZ。它仍然在ETH 许可证下,并且位于WayBack 存档中。

关于 Oberon 语言的常见问题

注意:与 Oberon 语言或编译器无关的问题将在ETH Oberon 系统上的 FAQ中处理。

一般

  1. 您认为使用 Oberon 而不是其他广泛使用的编程语言有什么优势?

样式

  1. Reader、Scanner 和 Writer 的词源是什么?
  2. 可以推荐哪些命名约定?

类型

  1. 记录的大小是多少?
  2. 为什么我不能将一个变量分配给另一个相同类型的变量?
  3. 编译器如何处理整数常量?

编程提示

  1. Oberon 源代码中可以使用最长的标识符是什么?标识符的多少个字符被视为唯一的?
  2. 如何在 Oberon 中处理 16 位无符号数?
  3. 如何强制转换变量?我正在寻找 C 中 (Process) c; 的等效项。
  4. 如何快速将 REAL 变量强制转换为 INTEGER?(准实时)
  5. 有没有办法提高实时应用程序的计算速度?
  6. 如何尽可能均匀地分布随机数?我需要用随机数填充 13*13*13 矩阵。
  7. Oberon 中是否有任何按位运算符可用?
  8. 如何将常量值直接写入内存?
  9. 变量初始化可能存在错误
  10. 有没有便携的方法来识别过程的调用者?

编译器

  1. 如何在源代码中定位错误?
  2. 为什么 \z 选项会导致生成更大的代码?
  3. 是否有用于 Oberon 编译器的测试集?

内置汇编器

  1. 我在哪里可以找到汇编器文档?
  2. 记录中字段的顺序是什么?
  3. 将任何内容强制转换为 BOOLEAN 时,编译器的行为异常。
  4. 对 SYSTEM.BYTE 进行操作的正确语法是什么?
  5. 如何从汇编器例程调用过程?
  6. 如何快速强制转换大量变量?
  7. 如何使用内联过程
一般
  1. 您认为使用 Oberon 而不是其他广泛使用的编程语言有什么优势?
    A
    :Antonio Cisternino 报道了以下内容

阅读 1980 年霍夫施塔特撰写的旧书“哥德尔、埃舍尔、巴赫:一条永恒的金带”,我在第 X 章中找到了以下内容

“用不同的语言编程就像用不同的调性作曲一样,尤其是在键盘上工作时。如果你已经学习或创作了许多调性的作品,每个调性都会有它自己的特殊情感氛围。此外,某些类型的音型在某个调性中“顺手可得”,但在另一个调性中却很笨拙。因此,你的调性选择会引导你。在某种程度上,即使是同音异名的调性,比如 C# 和 D♭,在感觉上也截然不同。这表明记谱系统如何在塑造最终产品中发挥重要作用。”

我认为这是一种很好的表达方式,即多种语言可能有助于解决复杂问题,如果每种语言都用于发挥其优势。它与 CLR 相关,而音乐背景恰好与 C# 的名称相符。

样式
  1. Reader、Scanner 和 Writer 的词源是什么?“er” 后缀表明它们是过程,但实际上它们是记录。
    A
    :Reader、Scanner 和 Writer 是名词,而不是动词,因此,根据 Reiser 和 Wirth 在“Programming in Oberon”中的命名指南进行推断,它们适合作为类型名称。
  1. 可以推荐哪些命名约定?
    A
    :摘自汉斯佩特·莫森博克的“面向对象的 Oberon-2 编程”
    Name of     Begin with         1st letter   Examples 
    ------------------------------------------------------------- 
    Constants   Noun or Adjective  Small        version, wordSize 
    Variables   Noun or Adjective  Small        full 
    Types       Noun               Capital      File, TextFrame 
    Procedures  Verb               Capital      WriteString 
    Functions   Noun or            Capital      Position 
                Adjective          Capital      Empty, Equal 
    Module      Noun               Capital      Files, TextFrames

    在声明指针类型 X 时,指向的记录应称为 XDesc,例如:

    TYPE List = POINTER TO ListDesc;
         ListDesc = RECORD ... END;
类型
  1. 以下两个定义产生了两个明显不同的尺寸
    TYPE RecA = RECORD 
        Elements : ARRAY 25 OF SYSTEM.BYTE; 
        END; 
       
    TYPE RecB = ARRAY 25 OF SYSTEM.BYTE;

    当我查看 RecA 的 SIZE 时,我看到 28,当我查看 RecB 的 SIZE 时,我看到 25(!)因此,如果我形成一个使用 UDP 发送的数据包,并且我依赖于使用 RecA 设置的结构的大小为 25,那么我就有麻烦了。这里发生了什么?当我使用 Delphi 或 M2 构建数据文件,并尝试使用 Oberon 读取同一个文件时,这将再次出现。
    A1
    :记录的大小始终在末尾填充到 4 字节的倍数。

    A2:读取文件的最佳可移植方法是逐字节读取,使用 *、DIV 和 MOD 显式转换字段。只有在需要处理大型文件的性能时,才应该使用内存映射记录。

    Files 模块提供用于读取小端序值的例程(ReadInt、ReadLInt)。
  2. 我无法将两个具有相同类型(err 113)的变量赋值。为什么?
    A
    :两个变量只有在具有相同的类型时才兼容,而不是具有相同的类型结构。以下示例展示了两个类似但不同的类型
    TYPE StudentGroup = RECORD  count: LONGINT  END; 
         PotatoBag = RECORD  count: LONGINT  END;
    
    VAR  s: StudentGroup; 
         p: PotatoBag; 
         ... 
         s := p;     (* err 113 here! *)

    仅仅因为它们具有相同的结构,并不意味着它们是兼容的。你会将学生和土豆进行比较吗?然而,当使用匿名类型(即在运行时声明的类型)时,这个问题就不那么明显了。在这种情况下,类型始终是不兼容的

    VAR  a: RECORD  count: LONGINT  END; 
         b: RECORD  count: LONGINT  END;

    尽管这两种类型看起来相同,但它们是不兼容的,因为它们具有不同的声明。要使 a 和 b 兼容,必须使用以下任一方法声明类型:

    VAR a, b: RECORD  count: LONGINT  END;

    TYPE MyType = RECORD  count: LONGINT  END; 
    ...
    VAR  a: MyType; b: MyType;

    在这种常见的过程参数情况下

    PROCEDURE P(a: ARRAY 32 OF CHAR);

    没有任何类型会与 a 具有相同的声明,因此没有任何变量会与 a 兼容!

    此规则只有一个例外:开放数组。在这种情况下,两个变量的基类型必须兼容。

    这允许在过程参数中使用匿名开放数组。
  3. 似乎编译器将整数常量视为 INTEGER 类型。我不知道它如何处理大型整数值,例如 40000。我怀疑它将其视为 LONGINT。它似乎没有将诸如 125 之类的值视为字节。
    A
    :整数常量具有可以容纳它的最小整数类型。因此 -128、0 和 127 将是 SHORTINT,-32768、128 和 32767 INTEGER,以及 -32769 和 32768 LONGINT。在低级代码中,为了确保大小,请使用 SYSTEM.VAL 来强制转换它。SYSTEM.VAL 应在 SYSTEM 例程 GET/PUT、PORTIN/PORTOUT、GETREG/PUTREG 的第二个参数中使用。或者,你也可以写
    SYSTEM.PUT(Adr, CHR(255)) 
    SYSTEM.PUT(Adr, 0FFX) 
    SYSTEM.PUT(Adr, SYSTEM.VAL(INTEGER, myconst))
编程提示
  1. Oberon 源代码中可以使用最长的标识符是什么?标识符的多少个字符被视为唯一的?
    A
    : 32. 32.
  2. 如何在 Oberon 中处理 16 位无符号数?
    A
    :将该数字转换为 32 位 LONGINT 值。例如
    VAR  x: LONGINT; y: INTEGER; 
         ...
         x := LONG(y) MOD 10000H
    MOD 被有效地编译为逻辑 AND 操作,因为除数是 2 的常数幂。
  3. 如何强制转换变量?我正在寻找 C 中 (Process) c; 的等效项。
    A
    :使用类型保护 - 参见“Programming in Oberon”中的第 11.2 章。使用 SYSTEM.VAL 进行强制转换不是一个好主意,因为它会破坏垃圾收集器。
  4. 如何快速将 REAL 变量强制转换为 INTEGER?
    A
    :使用内置汇编器,如所述
  5. 有没有办法提高实时应用程序的计算速度?
    A
    :正在开发一组CORDIC 算法
  6. 如何尽可能均匀地分布随机数?我需要用随机数填充 13*13*13 矩阵。
    A
    :RandomNumbers.Uniform() 是一个第一近似值。除非你只是玩游戏,否则你应该避免它。随机数充满了技巧。如果你喜欢阅读,可以看看 Knuth 的新版或 George Marsaglia 的论文。关于一般参考,请参见维基百科。一些常见的生成器可以在贡献中找到。
  7. Oberon 中是否有任何按位运算符可用?
    A
    :使用 SET 类型及其运算符。以下是运算符及其按位等效项。
    +     union                 bitwise or
    *     intersection          bitwise and 
    -     difference 
    /     symmetric difference  bitwise xor 
    IN    element test          bit test 
    INCL  element insert        bit set 
    EXCL  element remove        bit clear

    你可以使用 SYSTEM.VAL(SET, intexpr) 将表达式转换为集合,使用 SYSTEM.VAL(LONGINT, setexpr) 将表达式从集合转换为集合。

    但是,最好直接声明 SET 变量,这些变量可以方便地用于内联。这比声明 LONGINT 变量然后使用 SYSTEM.GET/PUT 将它们从 SET 类型变量转换或转换为 SET 类型变量更好。也应避免使用 SYSTEM.VAL。
  8. 如何将常量值直接写入内存?
    A
    :
    PROCEDURE P;
    CODE {SYSTEM.i386}
      MOV DWORD PTR @1234H, 15
    END P;
    
    000AH: C7 05 34 12 00 00 0F 00 00 00       MOV     [4660],15

    这相当于使用 MS Tools 中的以下操作:

    mov dword ptr ds:[adr], imm    giving the code: C7 05 adr imm
  9. 变量初始化可能存在错误。
    A
    :看看这个简短的过程
    PROCEDURE demo( VAR a: ARRAY OF INTEGER ); 
    VAR i: INTEGER; 
    BEGIN 
       (* i := 0; *) 
       WHILE i < LEN(a) DO a[i] := 0; INC(i) END 
    END demo;

    尽管 “i” 的初始化缺失,但此过程在 PC Oberon 上有效。在 Mac Oberon 上,它大部分时间都会失败,因为 “i” 具有随机值,但这不是编译器错误造成的。

    在 PC Oberon 上,整个堆栈帧在过程进入时被清除。因此,缺失的初始化(为零)不会引起注意。
  10. 有没有便携的方法来识别过程的调用者?
    A
    :由于 ETH Oberon 在所有版本中都没有完整的元编程功能,因此不存在便携的方法。但是,以下方法在 Native Oberon 和 Windows Oberon 上有效。
    MODULE Temp;
      IMPORT SYSTEM, Kernel, Out;
      PROCEDURE Identify(VAR modname: ARRAY OF CHAR; VAR pc: LONGINT); 
      VAR ebp, eip: LONGINT; m: Kernel.Module; 
      BEGIN 
        SYSTEM.GETREG(SYSTEM.EBP, ebp); 
        SYSTEM.GET(ebp, ebp);  (* stack frame of caller *) 
        SYSTEM.GET(ebp+4, eip);  (* return address from caller *) 
        m := Kernel.GetMod(eip); 
        IF m # NIL THEN 
          COPY(m.name, modname); pc := eip - SYSTEM.ADR(m.code[0]) 
        ELSE 
          modname[0] := 0X; pc := MAX(LONGINT) 
        END 
      END Identify;
      PROCEDURE Test*; 
      VAR name: ARRAY 32 OF CHAR; pc: LONGINT; 
      BEGIN 
        Identify(name, pc); 
        Out.String(name); Out.String(" PC="); Out.Int(pc, 1); Out.Ln 
      END Test;
     END Temp.

    这将为您提供调用模块的名称和 PC 偏移量,您可以将其与 Compiler.Compile \f 一起使用来查找调用位置。

    如果您只是为了调试,请考虑编写一个 HALT 语句,它还会提供堆栈回溯。在 Native Oberon 上,您可以使用 hack HALT(MAX(INTEGER)) 来实现一个会导致陷阱然后继续运行的中断。

    Bluebottle:需要对文本进行以下编辑以适应 Bluebottle
    将 Kernel 替换为 AosModules
    将 Kernel.Module 替换为 AosModules.Module
    将 Kernel.GetMod 替换为 AosModules.ThisModuleByAdr
编译器
  1. 如何在源代码中定位错误?
    A
    :
    1. 在 System.Log 中使用 MR+MR 点击选择错误报告行
    2. 按 F1 标记 (*) 源代码查看器
    3. 激活 System.Log 菜单栏中的 [Locate] 按钮
  2. 为什么 \z 选项会导致生成更大的代码?
    A
    :初始化整个局部变量块更简单:这是通过一个循环向堆栈写入零来完成的。只初始化指针更复杂,因为只有指针必须(单独)初始化。最坏情况示例
    VAR x: ARRAY 64 OF RECORD ..... z: PTR .... END;

    初始化所有

    LOAD size 
    WHILE size > 0 
        PUSH 0 
        DEC size 
    END

    只初始化指针

    MOV EAX, 0 
    MOV offs0[EBP], EAX 
    MOV offs1[EBP], EAX 
    ... 
    MOV offs63[EBP], EAX
    另一方面,只初始化指针更快。
  3. 是否有用于 Oberon 编译器的测试集?
    A
    :正在构建测试套件。一些测试已经可用。参见 Project Hostess Homepage
内置汇编器
  1. 我在哪里可以找到汇编器文档?
    A
    :从 PC Native Oberon - 编译器 开始。然后,从 Jacques Eloff 关于汇编器的书面良好且内容丰富的文档中获得一些灵感,该文档用于教授低级编程课程。
  2. 给定类型声明
    TYPE 
        RecDesc = RECORD 
            ch: CHAR; 
            val: LONGINT 
        END;

    如果一个过程接受一个 VAR 参数,例如 r: RecDesc,则记录字段的访问方式如下

    MOV EBX, 8[EBP] 
    MOV BYTE 0[EBX], 65            ;r.ch := 'A'; 
    MOV DWORD 4[EBX], 0            ;r.val := 0;

    但是,当在过程中局部声明一个记录类型的变量时,字段会被颠倒

    PROCEDURE A; 
        VAR r: RecType; 
        CODE {SYSTEM.i386} 
            MOV -8[EBP], 65       ;r.ch := 'A'; 
            MOV -4[EBP], 0        ;r.val := 0; 
        END A;

    是否有特殊原因导致这种情况?
    A
    : 字段并没有颠倒!偏移量 -8 比 -4 的地址更小。您可以从另一个角度来看待它

    LEA EAX, -8[EBP]      ;load record base address 
    MOV BYTE 0[EAX], 65   ;r.ch := 'A'; 
    MOV DWORD 4[EAX], 0   ;r.val := 0;
  3. 编译器的行为在将任何东西转换为 BOOLEAN 时很不寻常。例如,我经常从 I/O 端口输入数据,它会与控制字进行掩码运算,然后检查结果是 TRUE 还是 FALSE。在其他我熟悉的编译器中,如果传入字的低字节中设置了任何位,则转换为布尔类型后将得到 TRUE。也就是说,任何设置的位都会产生 TRUE。我发现 Oberon 编译器似乎不符合这种情况。我现在所做的是,将字节与零进行比较。这不是什么问题,但我必须注意这一点。
    A
    : 这种行为与 Pascal 一致,在 Pascal 中,布尔类型被定义为枚举类型 BOOLEAN = (FALSE, TRUE),即 ORD(FALSE) = 0 且 ORD(TRUE) = 1。
  4. VAR b: SYSTEM.BYTE; 
    BEGIN 
        b:= 2; 
        IF b = 0 THEN 
    END;

    在 IF 行,我会得到编译器错误,提示无效操作数。如果我将 b 转换为整数或将 0 转换为字节,则可以正常工作。
    A
    : 只有在 SYSTEM.BYTE 上定义了赋值,而没有定义比较。对于 8 位值,最好使用 CHAR 变量,因此

    VAR b: CHAR;
    BEGIN 
        b := 2X;  (* or CHR(2) *) 
        IF b = 0X THEN 
    END
    要转换回 INTEGER,请使用 ORD(b)。
  5. 如何从汇编程序例程调用过程?问题在于汇编程序只允许使用变量,而不允许使用例程外部的过程。
    A
    : 使用过程变量,例如以下示例
    MODULE MyModule; 
    IMPORT SYSTEM, Out; 
    VAR P: PROCEDURE (n: LONGINT); 
    PROCEDURE WriteRegister32(n: LONGINT); 
    BEGIN 
        Out.Int(n, 0); Out.Ln 
    END WriteRegister32;
    
    PROCEDURE AsmCode; 
    CODE {SYSTEM.i386} 
        PUSH 12345678 
        CALL P 
    END AsmCode;
    
    PROCEDURE Test*; 
    BEGIN 
        AsmCode 
    END Test;
    
    BEGIN 
        P := WriteRegister32 
    END MyModule.
  6. 如何快速转换大量变量?我必须处理大量图片数据,以准实时方式将 REAL 值转换为 INTEGER。使用 ENTIER 进行此操作太慢了。
    A
    : 使用此 模型过程.
  7. 如何使用内联过程。
    A
    : 内联过程是一个不会被调用的过程,而是文本插入到调用者过程中的过程。Jacques Eloff 的 汇编程序描述 中包含有关如何使用它们的详细文档。

2002 年 8 月 22 日 - 版权所有 © 2002 ETH Zürich。保留所有权利。
电子邮件:oberon-web at inf.ethz.ch
主页:http://www.ethoberon.ethz.ch/

华夏公益教科书