跳轉到內容

編譯器構造/已知語言概念實現

來自華夏公益教科書,開放世界開放圖書

已知語言概念實現

[編輯 | 編輯原始碼]

由於本書是關於編譯器的,以下研究沒有詳細說明語法或語言設計,而是實現技術。如果您熟悉以下語言,瞭解這些熟悉的概念(如動態繫結)的實現方式可能有助於理解編譯器。

此外,我們沒有考慮對語言設計決策的實際影響。一些棘手的情況,比如引用依賴的問題或運算子的優先順序,在大多數情況下可能不會發生,但這些通常是語言實現者的關注點。

在 Java 中,有四種方法呼叫,分別是 invokestaticinvokespecialinvokevirtualinvokeinterface。顧名思義,第一個用於呼叫靜態方法,其餘用於呼叫例項方法。由於靜態方法不能被覆蓋,因此 invokestatic 非常簡單;它本質上與在 C 中呼叫函式相同。

我們現在來看看例項方法呼叫的機制。考慮以下程式碼片段。

class A {
  public static int f () { return 1; }
  private int g_private () { return 2; }
  public final int g_final () { return 3; }
  public int g_non_final () { return 4; }

  public void test (A a) {
    a.f ();  // static; this is always 1.
    a.g_private ();  // special; this is always 2.
    a.g_final (); // special; this is always 3.
    a.g_non_final (); // virtual; this may be 4 or something else.
  }
}

class B extends A {
  public int g_non_final () { return 6; }
}

class C extends B {
  public int g_non_final () { return 7; }
  public int foo () { return A.this.g_non_final (); }
}

invokestatic 使用對類名和方法名的引用被呼叫,並從堆疊中彈出引數。表示式 A.f (2) 被編譯為

iconst_2           // push a constant 2 onto the stack 
invokestatic A.f   // invoke a static method
// the return value is at the top of the stack.

在 Java 中,私有方法不能被覆蓋。因此,必須根據類來呼叫方法,而不管物件是如何建立的。invokespecial 允許這樣做;該指令與 invokestatic 相同,除了它除了提供的引數外,還彈出物件引用。到目前為止,動態繫結沒有使用,並且在執行時不需要有關私有方法的繫結資訊。

具體來說,invokespecial 可以用於 (1) 呼叫私有方法或 (2) 呼叫超類的某個方法(包括超類的建構函式,即 <init>)。要呼叫除 <init> 之外的超類方法,必須像 super.f () 一樣編寫,其中 f 是超類方法的名稱。

在語義上,invokeinterfaceinvokevirtual 不同,但它可以給編譯器關於呼叫的提示。

類方法

[編輯 | 編輯原始碼]

類方法可以用 static 限定符定義。私有類方法可以在同一個物件中,如果它們屬於不同的類。兩個公共類方法不能在同一個物件中;換句話說,類方法不能被覆蓋。這也意味著 final 限定符對類方法在語義上是無意義的。

每個欄位都是根據類訪問的。考慮以下內容。

class A {
  public int i = 2;
}

class B extends A {
  public int i = 3;
}
B b = new B ();
A a = b;
b.i++;  // this would be 3 + 1 = 4
a.i++;  // this would be 2 + 1 = 3

換句話說,訪問控制修飾符(無、公共、私有和受保護)隻影響類的客戶端是否可以訪問給定欄位。這意味著 Java 虛擬機器可能會忽略訪問標誌,以相同的方式處理每個欄位。

Objective-C

[編輯 | 編輯原始碼]

物件和欄位

[編輯 | 編輯原始碼]

在 Objective-C 中,每個類都是 C 中的一個結構體。也就是說,

@interface A
{
  int a, b, c
}

@end

將被實現為

struct A {
  int a, b, c;
  .... // some runtime information
};

因此,由於 Objective-C 中的每個物件都是對堆中記憶體塊的指標。因此,訪問欄位的方式與訪問結構體成員的方式相同。也就是說,

id obj = [A alloc];

這種方案的含義是,雖然物件自然適合非 OOP C 程式,但一個缺點是欄位不能被“隱藏”。也就是說,

@interface A
{
  @private
  int a;
}
@end

@interface B : A
{
  @private
  int a;
}
@end

這會導致重複成員錯誤。這與 Java 中的情況形成對比。

最後,由於方法的選擇是在執行時發生的(與 Java 或 C++ 中的情況相反),因此方法的處理方式與欄位不同。

在 Objective-C 中,方法的選擇是在執行時發生的。編譯器可能會發出關於可能型別錯誤名稱的警告,因為編譯器可以知道程式中定義的一組選擇器名稱。但是,這在語義上不是必需的;任何訊息都可以傳送到任何物件。

在語義上,訊息的傳送者會檢查給定物件是否響應訊息,如果沒有,則嘗試其超類,如果沒有,則嘗試其超類,依此類推。

可能會出現一個複雜情況,例如,當有兩個選擇器具有不同的返回值型別時。考慮以下情況。

@interface A { }
- (float) func: (int) i
@end

@interface B { }
- (int) func: (int) i
@end

在這種情況下,因為編譯器無法知道物件將響應哪個方法--(float) func 或 (int) func—,它無法生成傳送訊息的程式碼,因為返回浮點數通常與返回整數不同。

華夏公益教科書