編譯器構造/已知語言概念實現
由於本書是關於編譯器的,以下研究沒有詳細說明語法或語言設計,而是實現技術。如果您熟悉以下語言,瞭解這些熟悉的概念(如動態繫結)的實現方式可能有助於理解編譯器。
此外,我們沒有考慮對語言設計決策的實際影響。一些棘手的情況,比如引用依賴的問題或運算子的優先順序,在大多數情況下可能不會發生,但這些通常是語言實現者的關注點。
在 Java 中,有四種方法呼叫,分別是 invokestatic、invokespecial、invokevirtual、invokeinterface。顧名思義,第一個用於呼叫靜態方法,其餘用於呼叫例項方法。由於靜態方法不能被覆蓋,因此 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 是超類方法的名稱。
在語義上,invokeinterface 與 invokevirtual 不同,但它可以給編譯器關於呼叫的提示。
類方法可以用 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 中,每個類都是 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—,它無法生成傳送訊息的程式碼,因為返回浮點數通常與返回整數不同。