跳轉到內容

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/

華夏公益教科書